mirror of https://github.com/Chocobozzz/PeerTube
Generate a name for caption files
parent
a8b1b40485
commit
6302d599cd
|
@ -1,17 +1,17 @@
|
|||
import * as express from 'express'
|
||||
import { MVideoCaption } from '@server/types/models'
|
||||
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
|
||||
import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
|
||||
import { createReqFiles } from '../../../helpers/express-utils'
|
||||
import { logger } from '../../../helpers/logger'
|
||||
import { getFormattedObjects } from '../../../helpers/utils'
|
||||
import { CONFIG } from '../../../initializers/config'
|
||||
import { MIMETYPES } from '../../../initializers/constants'
|
||||
import { sequelizeTypescript } from '../../../initializers/database'
|
||||
import { federateVideoIfNeeded } from '../../../lib/activitypub/videos'
|
||||
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares'
|
||||
import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators'
|
||||
import { createReqFiles } from '../../../helpers/express-utils'
|
||||
import { MIMETYPES } from '../../../initializers/constants'
|
||||
import { getFormattedObjects } from '../../../helpers/utils'
|
||||
import { VideoCaptionModel } from '../../../models/video/video-caption'
|
||||
import { logger } from '../../../helpers/logger'
|
||||
import { federateVideoIfNeeded } from '../../../lib/activitypub/videos'
|
||||
import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
|
||||
import { CONFIG } from '../../../initializers/config'
|
||||
import { sequelizeTypescript } from '../../../initializers/database'
|
||||
import { MVideoCaptionVideo } from '@server/types/models'
|
||||
import { HttpStatusCode } from '../../../../shared/core-utils/miscs/http-error-codes'
|
||||
|
||||
const reqVideoCaptionAdd = createReqFiles(
|
||||
[ 'captionfile' ],
|
||||
|
@ -57,17 +57,19 @@ async function addVideoCaption (req: express.Request, res: express.Response) {
|
|||
const videoCaptionPhysicalFile = req.files['captionfile'][0]
|
||||
const video = res.locals.videoAll
|
||||
|
||||
const captionLanguage = req.params.captionLanguage
|
||||
|
||||
const videoCaption = new VideoCaptionModel({
|
||||
videoId: video.id,
|
||||
language: req.params.captionLanguage
|
||||
}) as MVideoCaptionVideo
|
||||
videoCaption.Video = video
|
||||
filename: VideoCaptionModel.generateCaptionName(captionLanguage),
|
||||
language: captionLanguage
|
||||
}) as MVideoCaption
|
||||
|
||||
// Move physical file
|
||||
await moveAndProcessCaptionFile(videoCaptionPhysicalFile, videoCaption)
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
await VideoCaptionModel.insertOrReplaceLanguage(video.id, req.params.captionLanguage, null, t)
|
||||
await VideoCaptionModel.insertOrReplaceLanguage(videoCaption, t)
|
||||
|
||||
// Update video update
|
||||
await federateVideoIfNeeded(video, false, t)
|
||||
|
|
|
@ -9,9 +9,9 @@ import {
|
|||
MThumbnail,
|
||||
MUser,
|
||||
MVideoAccountDefault,
|
||||
MVideoCaptionVideo,
|
||||
MVideoCaption,
|
||||
MVideoTag,
|
||||
MVideoThumbnailAccountDefault,
|
||||
MVideoThumbnail,
|
||||
MVideoWithBlacklistLight
|
||||
} from '@server/types/models'
|
||||
import { MVideoImport, MVideoImportFormattable } from '@server/types/models/video/video-import'
|
||||
|
@ -154,20 +154,16 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
|
|||
|
||||
const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo)
|
||||
|
||||
let thumbnailModel: MThumbnail
|
||||
|
||||
// Process video thumbnail from request.files
|
||||
thumbnailModel = await processThumbnail(req, video)
|
||||
let thumbnailModel = await processThumbnail(req, video)
|
||||
|
||||
// Process video thumbnail from url if processing from request.files failed
|
||||
if (!thumbnailModel && youtubeDLInfo.thumbnailUrl) {
|
||||
thumbnailModel = await processThumbnailFromUrl(youtubeDLInfo.thumbnailUrl, video)
|
||||
}
|
||||
|
||||
let previewModel: MThumbnail
|
||||
|
||||
// Process video preview from request.files
|
||||
previewModel = await processPreview(req, video)
|
||||
let previewModel = await processPreview(req, video)
|
||||
|
||||
// Process video preview from url if processing from request.files failed
|
||||
if (!previewModel && youtubeDLInfo.thumbnailUrl) {
|
||||
|
@ -199,15 +195,15 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
|
|||
for (const subtitle of subtitles) {
|
||||
const videoCaption = new VideoCaptionModel({
|
||||
videoId: video.id,
|
||||
language: subtitle.language
|
||||
}) as MVideoCaptionVideo
|
||||
videoCaption.Video = video
|
||||
language: subtitle.language,
|
||||
filename: VideoCaptionModel.generateCaptionName(subtitle.language)
|
||||
}) as MVideoCaption
|
||||
|
||||
// Move physical file
|
||||
await moveAndProcessCaptionFile(subtitle, videoCaption)
|
||||
|
||||
await sequelizeTypescript.transaction(async t => {
|
||||
await VideoCaptionModel.insertOrReplaceLanguage(video.id, subtitle.language, null, t)
|
||||
await VideoCaptionModel.insertOrReplaceLanguage(videoCaption, t)
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -227,7 +223,7 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
|
|||
return res.json(videoImport.toFormattedJSON()).end()
|
||||
}
|
||||
|
||||
function buildVideo (channelId: number, body: VideoImportCreate, importData: YoutubeDLInfo) {
|
||||
function buildVideo (channelId: number, body: VideoImportCreate, importData: YoutubeDLInfo): MVideoThumbnail {
|
||||
const videoData = {
|
||||
name: body.name || importData.name || 'Unknown name',
|
||||
remote: false,
|
||||
|
@ -252,7 +248,7 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You
|
|||
return video
|
||||
}
|
||||
|
||||
async function processThumbnail (req: express.Request, video: VideoModel) {
|
||||
async function processThumbnail (req: express.Request, video: MVideoThumbnail) {
|
||||
const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined
|
||||
if (thumbnailField) {
|
||||
const thumbnailPhysicalFile = thumbnailField[0]
|
||||
|
@ -268,7 +264,7 @@ async function processThumbnail (req: express.Request, video: VideoModel) {
|
|||
return undefined
|
||||
}
|
||||
|
||||
async function processPreview (req: express.Request, video: VideoModel) {
|
||||
async function processPreview (req: express.Request, video: MVideoThumbnail): Promise<MThumbnail> {
|
||||
const previewField = req.files ? req.files['previewfile'] : undefined
|
||||
if (previewField) {
|
||||
const previewPhysicalFile = previewField[0]
|
||||
|
@ -284,7 +280,7 @@ async function processPreview (req: express.Request, video: VideoModel) {
|
|||
return undefined
|
||||
}
|
||||
|
||||
async function processThumbnailFromUrl (url: string, video: VideoModel) {
|
||||
async function processThumbnailFromUrl (url: string, video: MVideoThumbnail) {
|
||||
try {
|
||||
return createVideoMiniatureFromUrl(url, video, ThumbnailType.MINIATURE)
|
||||
} catch (err) {
|
||||
|
@ -293,7 +289,7 @@ async function processThumbnailFromUrl (url: string, video: VideoModel) {
|
|||
}
|
||||
}
|
||||
|
||||
async function processPreviewFromUrl (url: string, video: VideoModel) {
|
||||
async function processPreviewFromUrl (url: string, video: MVideoThumbnail) {
|
||||
try {
|
||||
return createVideoMiniatureFromUrl(url, video, ThumbnailType.PREVIEW)
|
||||
} catch (err) {
|
||||
|
@ -303,7 +299,7 @@ async function processPreviewFromUrl (url: string, video: VideoModel) {
|
|||
}
|
||||
|
||||
function insertIntoDB (parameters: {
|
||||
video: MVideoThumbnailAccountDefault
|
||||
video: MVideoThumbnail
|
||||
thumbnailModel: MThumbnail
|
||||
previewModel: MThumbnail
|
||||
videoChannel: MChannelAccountDefault
|
||||
|
|
|
@ -23,7 +23,7 @@ lazyStaticRouter.use(
|
|||
)
|
||||
|
||||
lazyStaticRouter.use(
|
||||
LAZY_STATIC_PATHS.VIDEO_CAPTIONS + ':videoId-:captionLanguage([a-z]+).vtt',
|
||||
LAZY_STATIC_PATHS.VIDEO_CAPTIONS + ':filename',
|
||||
asyncMiddleware(getVideoCaption)
|
||||
)
|
||||
|
||||
|
@ -78,10 +78,7 @@ async function getPreview (req: express.Request, res: express.Response) {
|
|||
}
|
||||
|
||||
async function getVideoCaption (req: express.Request, res: express.Response) {
|
||||
const result = await VideosCaptionCache.Instance.getFilePath({
|
||||
videoId: req.params.videoId,
|
||||
language: req.params.captionLanguage
|
||||
})
|
||||
const result = await VideosCaptionCache.Instance.getFilePath(req.params.filename)
|
||||
if (!result) return res.sendStatus(HttpStatusCode.NOT_FOUND_404)
|
||||
|
||||
return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE.SERVER })
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { join } from 'path'
|
||||
import { CONFIG } from '../initializers/config'
|
||||
import * as srt2vtt from 'srt-to-vtt'
|
||||
import { createReadStream, createWriteStream, move, remove } from 'fs-extra'
|
||||
import { MVideoCaptionFormattable } from '@server/types/models'
|
||||
import { join } from 'path'
|
||||
import * as srt2vtt from 'srt-to-vtt'
|
||||
import { MVideoCaption } from '@server/types/models'
|
||||
import { CONFIG } from '../initializers/config'
|
||||
|
||||
async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: MVideoCaptionFormattable) {
|
||||
async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: MVideoCaption) {
|
||||
const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR
|
||||
const destination = join(videoCaptionsDir, videoCaption.getCaptionName())
|
||||
const destination = join(videoCaptionsDir, videoCaption.filename)
|
||||
|
||||
// Convert this srt file to vtt
|
||||
if (physicalFile.path.endsWith('.srt')) {
|
||||
|
@ -17,7 +17,7 @@ async function moveAndProcessCaptionFile (physicalFile: { filename: string, path
|
|||
}
|
||||
|
||||
// This is important in case if there is another attempt in the retry process
|
||||
physicalFile.filename = videoCaption.getCaptionName()
|
||||
physicalFile.filename = videoCaption.filename
|
||||
physicalFile.path = destination
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ async function doesVideoCaptionExist (video: MVideoId, language: string, res: Re
|
|||
if (!videoCaption) {
|
||||
res.status(HttpStatusCode.NOT_FOUND_404)
|
||||
.json({ error: 'Video caption not found' })
|
||||
.end()
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LAST_MIGRATION_VERSION = 575
|
||||
const LAST_MIGRATION_VERSION = 580
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import * as Sequelize from 'sequelize'
|
||||
|
||||
async function up (utils: {
|
||||
transaction: Sequelize.Transaction
|
||||
queryInterface: Sequelize.QueryInterface
|
||||
sequelize: Sequelize.Sequelize
|
||||
db: any
|
||||
}): Promise<void> {
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('videoCaption', 'filename', data)
|
||||
}
|
||||
|
||||
{
|
||||
const query = `UPDATE "videoCaption" SET "filename" = s.uuid || '-' || s.language || '.vtt' ` +
|
||||
`FROM (` +
|
||||
` SELECT "videoCaption"."id", video.uuid, "videoCaption".language ` +
|
||||
` FROM "videoCaption" INNER JOIN video ON video.id = "videoCaption"."videoId"` +
|
||||
`) AS s ` +
|
||||
`WHERE "videoCaption".id = s.id`
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
|
||||
{
|
||||
const data = {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.changeColumn('videoCaption', 'filename', data)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
|
@ -99,8 +99,6 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
|
|||
return Promise.resolve()
|
||||
})
|
||||
|
||||
logger.info('toto', { playlist, id: playlist.id })
|
||||
|
||||
const refreshedPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(playlist.id, null)
|
||||
|
||||
if (playlistObject.icon) {
|
||||
|
|
|
@ -56,6 +56,7 @@ import {
|
|||
MVideoAccountLightBlacklistAllFiles,
|
||||
MVideoAP,
|
||||
MVideoAPWithoutCaption,
|
||||
MVideoCaption,
|
||||
MVideoFile,
|
||||
MVideoFullLight,
|
||||
MVideoId,
|
||||
|
@ -90,7 +91,7 @@ async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVid
|
|||
// Fetch more attributes that we will need to serialize in AP object
|
||||
if (isArray(video.VideoCaptions) === false) {
|
||||
video.VideoCaptions = await video.$get('VideoCaptions', {
|
||||
attributes: [ 'language' ],
|
||||
attributes: [ 'filename', 'language' ],
|
||||
transaction
|
||||
})
|
||||
}
|
||||
|
@ -423,7 +424,14 @@ async function updateVideoFromAP (options: {
|
|||
await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t)
|
||||
|
||||
const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
|
||||
return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, c.url, t)
|
||||
const caption = new VideoCaptionModel({
|
||||
videoId: videoUpdated.id,
|
||||
filename: VideoCaptionModel.generateCaptionName(c.identifier),
|
||||
language: c.identifier,
|
||||
fileUrl: c.url
|
||||
}) as MVideoCaption
|
||||
|
||||
return VideoCaptionModel.insertOrReplaceLanguage(caption, t)
|
||||
})
|
||||
await Promise.all(videoCaptionsPromises)
|
||||
}
|
||||
|
@ -629,7 +637,14 @@ async function createVideo (videoObject: VideoObject, channel: MChannelAccountLi
|
|||
|
||||
// Process captions
|
||||
const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
|
||||
return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, c.url, t)
|
||||
const caption = new VideoCaptionModel({
|
||||
videoId: videoCreated.id,
|
||||
filename: VideoCaptionModel.generateCaptionName(c.identifier),
|
||||
language: c.identifier,
|
||||
fileUrl: c.url
|
||||
}) as MVideoCaption
|
||||
|
||||
return VideoCaptionModel.insertOrReplaceLanguage(caption, t)
|
||||
})
|
||||
await Promise.all(videoCaptionsPromises)
|
||||
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
import { join } from 'path'
|
||||
import { doRequestAndSaveToFile } from '@server/helpers/requests'
|
||||
import { CONFIG } from '../../initializers/config'
|
||||
import { FILES_CACHE } from '../../initializers/constants'
|
||||
import { VideoModel } from '../../models/video/video'
|
||||
import { VideoCaptionModel } from '../../models/video/video-caption'
|
||||
import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache'
|
||||
import { CONFIG } from '../../initializers/config'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { doRequestAndSaveToFile } from '@server/helpers/requests'
|
||||
|
||||
type GetPathParam = { videoId: string, language: string }
|
||||
class VideosCaptionCache extends AbstractVideoStaticFileCache <string> {
|
||||
|
||||
class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> {
|
||||
|
||||
private static readonly KEY_DELIMITER = '%'
|
||||
private static instance: VideosCaptionCache
|
||||
|
||||
private constructor () {
|
||||
|
@ -22,32 +18,28 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> {
|
|||
return this.instance || (this.instance = new this())
|
||||
}
|
||||
|
||||
async getFilePathImpl (params: GetPathParam) {
|
||||
const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language)
|
||||
async getFilePathImpl (filename: string) {
|
||||
const videoCaption = await VideoCaptionModel.loadWithVideoByFilename(filename)
|
||||
if (!videoCaption) return undefined
|
||||
|
||||
if (videoCaption.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.getCaptionName()) }
|
||||
if (videoCaption.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.filename) }
|
||||
|
||||
const key = params.videoId + VideosCaptionCache.KEY_DELIMITER + params.language
|
||||
return this.loadRemoteFile(key)
|
||||
return this.loadRemoteFile(filename)
|
||||
}
|
||||
|
||||
// Key is the caption filename
|
||||
protected async loadRemoteFile (key: string) {
|
||||
logger.debug('Loading remote caption file %s.', key)
|
||||
|
||||
const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER)
|
||||
|
||||
const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language)
|
||||
const videoCaption = await VideoCaptionModel.loadWithVideoByFilename(key)
|
||||
if (!videoCaption) return undefined
|
||||
|
||||
if (videoCaption.isOwned()) throw new Error('Cannot load remote caption of owned video.')
|
||||
|
||||
// Used to fetch the path
|
||||
const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId)
|
||||
const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoCaption.videoId)
|
||||
if (!video) return undefined
|
||||
|
||||
const remoteUrl = videoCaption.getFileUrl(video)
|
||||
const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName())
|
||||
const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.filename)
|
||||
|
||||
await doRequestAndSaveToFile({ uri: remoteUrl }, destPath)
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
|
|||
return this.loadRemoteFile(thumbnail.Video.uuid)
|
||||
}
|
||||
|
||||
// Key is the video UUID
|
||||
protected async loadRemoteFile (key: string) {
|
||||
const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(key)
|
||||
if (!video) return undefined
|
||||
|
|
|
@ -166,6 +166,9 @@ async function createThumbnailFromFunction (parameters: {
|
|||
}) {
|
||||
const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters
|
||||
|
||||
// Remove old file
|
||||
if (existingThumbnail) await existingThumbnail.removeThumbnail()
|
||||
|
||||
const thumbnail = existingThumbnail || new ThumbnailModel()
|
||||
|
||||
thumbnail.filename = filename
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
UpdatedAt
|
||||
} from 'sequelize-typescript'
|
||||
import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
|
||||
import { MVideoAccountLight, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models'
|
||||
import { MVideoAccountLight, MVideoCaption, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/types/models'
|
||||
import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model'
|
||||
import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions'
|
||||
import { logger } from '../../helpers/logger'
|
||||
|
@ -24,6 +24,7 @@ import { CONFIG } from '../../initializers/config'
|
|||
import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants'
|
||||
import { buildWhereIdOrUUID, throwIfNotValid } from '../utils'
|
||||
import { VideoModel } from './video'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export enum ScopeNames {
|
||||
WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE'
|
||||
|
@ -44,6 +45,10 @@ export enum ScopeNames {
|
|||
@Table({
|
||||
tableName: 'videoCaption',
|
||||
indexes: [
|
||||
{
|
||||
fields: [ 'filename' ],
|
||||
unique: true
|
||||
},
|
||||
{
|
||||
fields: [ 'videoId' ]
|
||||
},
|
||||
|
@ -65,6 +70,10 @@ 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
|
||||
|
@ -88,12 +97,12 @@ export class VideoCaptionModel extends Model {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,15 +128,28 @@ export class VideoCaptionModel extends Model {
|
|||
return VideoCaptionModel.findOne(query)
|
||||
}
|
||||
|
||||
static insertOrReplaceLanguage (videoId: number, language: string, fileUrl: string, transaction: Transaction) {
|
||||
const values = {
|
||||
videoId,
|
||||
language,
|
||||
fileUrl
|
||||
static loadWithVideoByFilename (filename: string): Promise<MVideoCaptionVideo> {
|
||||
const query = {
|
||||
where: {
|
||||
filename
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: VideoModel.unscoped(),
|
||||
attributes: [ 'id', 'remote', 'uuid' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return VideoCaptionModel.upsert(values, { transaction, returning: true })
|
||||
.then(([ caption ]) => caption)
|
||||
return VideoCaptionModel.findOne(query)
|
||||
}
|
||||
|
||||
static async insertOrReplaceLanguage (caption: MVideoCaption, transaction: Transaction) {
|
||||
const existing = await VideoCaptionModel.loadByVideoIdAndLanguage(caption.videoId, caption.language)
|
||||
// Delete existing file
|
||||
if (existing) await existing.destroy({ transaction })
|
||||
|
||||
return caption.save({ transaction })
|
||||
}
|
||||
|
||||
static listVideoCaptions (videoId: number): Promise<MVideoCaptionVideo[]> {
|
||||
|
@ -156,6 +178,10 @@ export class VideoCaptionModel extends Model {
|
|||
return VideoCaptionModel.destroy(query)
|
||||
}
|
||||
|
||||
static generateCaptionName (language: string) {
|
||||
return `${uuidv4()}-${language}.vtt`
|
||||
}
|
||||
|
||||
isOwned () {
|
||||
return this.Video.remote === false
|
||||
}
|
||||
|
@ -170,16 +196,12 @@ export class VideoCaptionModel extends Model {
|
|||
}
|
||||
}
|
||||
|
||||
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: MVideoCaptionFormattable) {
|
||||
return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName())
|
||||
removeCaptionFile (this: MVideoCaption) {
|
||||
return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.filename)
|
||||
}
|
||||
|
||||
getFileUrl (video: MVideoAccountLight) {
|
||||
|
|
|
@ -471,7 +471,7 @@ export class VideoPlaylistModel extends Model {
|
|||
generateThumbnailName () {
|
||||
const extension = '.jpg'
|
||||
|
||||
return 'playlist-' + this.uuid + extension
|
||||
return 'playlist-' + uuidv4() + extension
|
||||
}
|
||||
|
||||
getThumbnailUrl () {
|
||||
|
|
|
@ -2,24 +2,25 @@
|
|||
|
||||
import 'mocha'
|
||||
import * as chai from 'chai'
|
||||
import { Video, VideoPlaylistPrivacy } from '@shared/models'
|
||||
import {
|
||||
addVideoInPlaylist,
|
||||
createVideoPlaylist,
|
||||
getOEmbed,
|
||||
getVideosList,
|
||||
ServerInfo,
|
||||
setAccessTokensToServers,
|
||||
setDefaultVideoChannel,
|
||||
uploadVideo,
|
||||
createVideoPlaylist,
|
||||
addVideoInPlaylist
|
||||
uploadVideo
|
||||
} from '../../../../shared/extra-utils'
|
||||
import { cleanupTests, flushAndRunServer } from '../../../../shared/extra-utils/server/servers'
|
||||
import { VideoPlaylistPrivacy } from '@shared/models'
|
||||
|
||||
const expect = chai.expect
|
||||
|
||||
describe('Test services', function () {
|
||||
let server: ServerInfo = null
|
||||
let playlistUUID: string
|
||||
let video: Video
|
||||
|
||||
before(async function () {
|
||||
this.timeout(30000)
|
||||
|
@ -36,7 +37,7 @@ describe('Test services', function () {
|
|||
await uploadVideo(server.url, server.accessToken, videoAttributes)
|
||||
|
||||
const res = await getVideosList(server.url)
|
||||
server.video = res.body.data[0]
|
||||
video = res.body.data[0]
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -57,23 +58,23 @@ describe('Test services', function () {
|
|||
token: server.accessToken,
|
||||
playlistId: res.body.videoPlaylist.id,
|
||||
elementAttrs: {
|
||||
videoId: server.video.id
|
||||
videoId: video.id
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
it('Should have a valid oEmbed video response', async function () {
|
||||
const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid
|
||||
const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + video.uuid
|
||||
|
||||
const res = await getOEmbed(server.url, oembedUrl)
|
||||
const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' +
|
||||
`src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` +
|
||||
`src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` +
|
||||
'frameborder="0" allowfullscreen></iframe>'
|
||||
const expectedThumbnailUrl = 'http://localhost:' + server.port + '/lazy-static/previews/' + server.video.uuid + '.jpg'
|
||||
const expectedThumbnailUrl = 'http://localhost:' + server.port + video.previewPath
|
||||
|
||||
expect(res.body.html).to.equal(expectedHtml)
|
||||
expect(res.body.title).to.equal(server.video.name)
|
||||
expect(res.body.title).to.equal(video.name)
|
||||
expect(res.body.author_name).to.equal(server.videoChannel.displayName)
|
||||
expect(res.body.width).to.equal(560)
|
||||
expect(res.body.height).to.equal(315)
|
||||
|
@ -101,18 +102,18 @@ describe('Test services', function () {
|
|||
})
|
||||
|
||||
it('Should have a valid oEmbed response with small max height query', async function () {
|
||||
const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + server.video.uuid
|
||||
const oembedUrl = 'http://localhost:' + server.port + '/videos/watch/' + video.uuid
|
||||
const format = 'json'
|
||||
const maxHeight = 50
|
||||
const maxWidth = 50
|
||||
|
||||
const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth)
|
||||
const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' +
|
||||
`src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` +
|
||||
`src="http://localhost:${server.port}/videos/embed/${video.uuid}" ` +
|
||||
'frameborder="0" allowfullscreen></iframe>'
|
||||
|
||||
expect(res.body.html).to.equal(expectedHtml)
|
||||
expect(res.body.title).to.equal(server.video.name)
|
||||
expect(res.body.title).to.equal(video.name)
|
||||
expect(res.body.author_name).to.equal(server.videoChannel.displayName)
|
||||
expect(res.body.height).to.equal(50)
|
||||
expect(res.body.width).to.equal(50)
|
||||
|
|
|
@ -24,6 +24,8 @@ import { VideoCaption } from '../../../../shared/models/videos/caption/video-cap
|
|||
const expect = chai.expect
|
||||
|
||||
describe('Test video captions', function () {
|
||||
const uuidRegex = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
|
||||
|
||||
let servers: ServerInfo[]
|
||||
let videoUUID: string
|
||||
|
||||
|
@ -83,13 +85,13 @@ describe('Test video captions', function () {
|
|||
const caption1: VideoCaption = res.body.data[0]
|
||||
expect(caption1.language.id).to.equal('ar')
|
||||
expect(caption1.language.label).to.equal('Arabic')
|
||||
expect(caption1.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-ar.vtt')
|
||||
expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$'))
|
||||
await testCaptionFile(server.url, caption1.captionPath, 'Subtitle good 1.')
|
||||
|
||||
const caption2: VideoCaption = res.body.data[1]
|
||||
expect(caption2.language.id).to.equal('zh')
|
||||
expect(caption2.language.label).to.equal('Chinese')
|
||||
expect(caption2.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-zh.vtt')
|
||||
expect(caption2.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-zh.vtt$'))
|
||||
await testCaptionFile(server.url, caption2.captionPath, 'Subtitle good 2.')
|
||||
}
|
||||
})
|
||||
|
@ -117,7 +119,7 @@ describe('Test video captions', function () {
|
|||
const caption1: VideoCaption = res.body.data[0]
|
||||
expect(caption1.language.id).to.equal('ar')
|
||||
expect(caption1.language.label).to.equal('Arabic')
|
||||
expect(caption1.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-ar.vtt')
|
||||
expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$'))
|
||||
await testCaptionFile(server.url, caption1.captionPath, 'Subtitle good 2.')
|
||||
}
|
||||
})
|
||||
|
@ -148,7 +150,7 @@ describe('Test video captions', function () {
|
|||
const caption1: VideoCaption = res.body.data[0]
|
||||
expect(caption1.language.id).to.equal('ar')
|
||||
expect(caption1.language.label).to.equal('Arabic')
|
||||
expect(caption1.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-ar.vtt')
|
||||
expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-ar.vtt$'))
|
||||
|
||||
const expected = 'WEBVTT FILE\r\n' +
|
||||
'\r\n' +
|
||||
|
@ -185,7 +187,7 @@ describe('Test video captions', function () {
|
|||
|
||||
expect(caption.language.id).to.equal('zh')
|
||||
expect(caption.language.label).to.equal('Chinese')
|
||||
expect(caption.captionPath).to.equal('/lazy-static/video-captions/' + videoUUID + '-zh.vtt')
|
||||
expect(caption.captionPath).to.match(new RegExp('^/lazy-static/video-captions/' + uuidRegex + '-zh.vtt$'))
|
||||
await testCaptionFile(server.url, caption.captionPath, 'Subtitle good 2.')
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { PickWith } from '@shared/core-utils'
|
||||
import { VideoCaptionModel } from '../../../models/video/video-caption'
|
||||
import { FunctionProperties, PickWith } from '@shared/core-utils'
|
||||
import { MVideo, MVideoUUID } from './video'
|
||||
|
||||
type Use<K extends keyof VideoCaptionModel, M> = PickWith<VideoCaptionModel, K, M>
|
||||
|
@ -22,6 +22,6 @@ export type MVideoCaptionVideo =
|
|||
// Format for API or AP object
|
||||
|
||||
export type MVideoCaptionFormattable =
|
||||
FunctionProperties<MVideoCaption> &
|
||||
MVideoCaption &
|
||||
Pick<MVideoCaption, 'language'> &
|
||||
Use<'Video', MVideoUUID>
|
||||
|
|
|
@ -5,7 +5,7 @@ import { ChildProcess, exec, fork } from 'child_process'
|
|||
import { copy, ensureDir, pathExists, readdir, readFile, remove } from 'fs-extra'
|
||||
import { join } from 'path'
|
||||
import { randomInt } from '../../core-utils/miscs/miscs'
|
||||
import { VideoChannel } from '../../models/videos'
|
||||
import { Video, VideoChannel } from '../../models/videos'
|
||||
import { buildServerDirectory, getFileSize, isGithubCI, root, wait } from '../miscs/miscs'
|
||||
import { makeGetRequest } from '../requests/requests'
|
||||
|
||||
|
|
Loading…
Reference in New Issue