Generate a name for caption files

pull/3746/head
Chocobozzz 2021-02-15 14:08:16 +01:00 committed by Chocobozzz
parent a8b1b40485
commit 6302d599cd
18 changed files with 186 additions and 110 deletions

View File

@ -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)

View File

@ -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

View File

@ -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 })

View File

@ -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
}

View File

@ -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
}

View File

@ -24,7 +24,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
// ---------------------------------------------------------------------------
const LAST_MIGRATION_VERSION = 575
const LAST_MIGRATION_VERSION = 580
// ---------------------------------------------------------------------------

View File

@ -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
}

View File

@ -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) {

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -471,7 +471,7 @@ export class VideoPlaylistModel extends Model {
generateThumbnailName () {
const extension = '.jpg'
return 'playlist-' + this.uuid + extension
return 'playlist-' + uuidv4() + extension
}
getThumbnailUrl () {

View File

@ -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)

View File

@ -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.')
}
})

View File

@ -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>

View File

@ -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'