Remove previous thumbnail if needed

pull/3746/head
Chocobozzz 2021-02-16 08:50:40 +01:00 committed by Chocobozzz
parent 6302d599cd
commit a35a22797c
15 changed files with 274 additions and 119 deletions

View File

@ -23,6 +23,7 @@
"consistent-as-needed"
],
"padded-blocks": "off",
"prefer-regex-literals": "off",
"no-async-promise-executor": "off",
"dot-notation": "off",
"promise/param-names": "off",

View File

@ -95,7 +95,7 @@ function doesVideoExist (keepOnlyOwned: boolean) {
function doesThumbnailExist (keepOnlyOwned: boolean, type: ThumbnailType) {
return async (file: string) => {
const thumbnail = await ThumbnailModel.loadWithVideoByName(file, type)
const thumbnail = await ThumbnailModel.loadByFilename(file, type)
if (!thumbnail) return false
if (keepOnlyOwned) {

View File

@ -173,7 +173,11 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
const thumbnailField = req.files['thumbnailfile']
const thumbnailModel = thumbnailField
? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist, false)
? await createPlaylistMiniatureFromExisting({
inputPath: thumbnailField[0].path,
playlist: videoPlaylist,
automaticallyGenerated: false
})
: undefined
const videoPlaylistCreated = await sequelizeTypescript.transaction(async t => {
@ -211,7 +215,11 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
const thumbnailField = req.files['thumbnailfile']
const thumbnailModel = thumbnailField
? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylistInstance, false)
? await createPlaylistMiniatureFromExisting({
inputPath: thumbnailField[0].path,
playlist: videoPlaylistInstance,
automaticallyGenerated: false
})
: undefined
try {
@ -474,7 +482,12 @@ async function generateThumbnailForPlaylist (videoPlaylist: MVideoPlaylistThumbn
}
const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, videoMiniature.filename)
const thumbnailModel = await createPlaylistMiniatureFromExisting(inputPath, videoPlaylist, true, true)
const thumbnailModel = await createPlaylistMiniatureFromExisting({
inputPath,
playlist: videoPlaylist,
automaticallyGenerated: true,
keepOriginal: true
})
thumbnailModel.videoPlaylistId = videoPlaylist.id

View File

@ -282,7 +282,7 @@ async function processPreview (req: express.Request, video: MVideoThumbnail): Pr
async function processThumbnailFromUrl (url: string, video: MVideoThumbnail) {
try {
return createVideoMiniatureFromUrl(url, video, ThumbnailType.MINIATURE)
return createVideoMiniatureFromUrl({ downloadUrl: url, video, type: ThumbnailType.MINIATURE })
} catch (err) {
logger.warn('Cannot generate video thumbnail %s for %s.', url, video.url, { err })
return undefined
@ -291,14 +291,14 @@ async function processThumbnailFromUrl (url: string, video: MVideoThumbnail) {
async function processPreviewFromUrl (url: string, video: MVideoThumbnail) {
try {
return createVideoMiniatureFromUrl(url, video, ThumbnailType.PREVIEW)
return createVideoMiniatureFromUrl({ downloadUrl: url, video, type: ThumbnailType.PREVIEW })
} catch (err) {
logger.warn('Cannot generate video preview %s for %s.', url, video.url, { err })
return undefined
}
}
function insertIntoDB (parameters: {
async function insertIntoDB (parameters: {
video: MVideoThumbnail
thumbnailModel: MThumbnail
previewModel: MThumbnail
@ -309,7 +309,7 @@ function insertIntoDB (parameters: {
}): Promise<MVideoImportFormattable> {
const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters
return sequelizeTypescript.transaction(async t => {
const videoImport = await sequelizeTypescript.transaction(async t => {
const sequelizeOptions = { transaction: t }
// Save video object in database
@ -339,4 +339,6 @@ function insertIntoDB (parameters: {
return videoImport
})
return videoImport
}

View File

@ -215,7 +215,7 @@ async function addVideo (req: express.Request, res: express.Response) {
const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({
video,
files: req.files,
fallback: type => generateVideoMiniature(video, videoFile, type)
fallback: type => generateVideoMiniature({ video, videoFile, type })
})
// Create the torrent file

View File

@ -103,7 +103,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
if (playlistObject.icon) {
try {
const thumbnailModel = await createPlaylistMiniatureFromUrl(playlistObject.icon.url, refreshedPlaylist)
const thumbnailModel = await createPlaylistMiniatureFromUrl({ downloadUrl: playlistObject.icon.url, playlist: refreshedPlaylist })
await refreshedPlaylist.setAndSaveThumbnail(thumbnailModel, undefined)
} catch (err) {
logger.warn('Cannot generate thumbnail of %s.', playlistObject.id, { err })

View File

@ -313,7 +313,11 @@ async function updateVideoFromAP (options: {
let thumbnailModel: MThumbnail
try {
thumbnailModel = await createVideoMiniatureFromUrl(getThumbnailFromIcons(videoObject).url, video, ThumbnailType.MINIATURE)
thumbnailModel = await createVideoMiniatureFromUrl({
downloadUrl: getThumbnailFromIcons(videoObject).url,
video,
type: ThumbnailType.MINIATURE
})
} catch (err) {
logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err })
}
@ -362,7 +366,12 @@ async function updateVideoFromAP (options: {
if (videoUpdated.getPreview()) {
const previewUrl = getPreviewUrl(getPreviewFromIcons(videoObject), video)
const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
const previewModel = createPlaceholderThumbnail({
fileUrl: previewUrl,
video,
type: ThumbnailType.PREVIEW,
size: PREVIEWS_SIZE
})
await videoUpdated.addAndSaveThumbnail(previewModel, t)
}
@ -585,11 +594,14 @@ async function createVideo (videoObject: VideoObject, channel: MChannelAccountLi
const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to)
const video = VideoModel.build(videoData) as MVideoThumbnail
const promiseThumbnail = createVideoMiniatureFromUrl(getThumbnailFromIcons(videoObject).url, video, ThumbnailType.MINIATURE)
.catch(err => {
logger.error('Cannot create miniature from url.', { err })
return undefined
})
const promiseThumbnail = createVideoMiniatureFromUrl({
downloadUrl: getThumbnailFromIcons(videoObject).url,
video,
type: ThumbnailType.MINIATURE
}).catch(err => {
logger.error('Cannot create miniature from url.', { err })
return undefined
})
let thumbnailModel: MThumbnail
if (waitThumbnail === true) {
@ -597,81 +609,93 @@ async function createVideo (videoObject: VideoObject, channel: MChannelAccountLi
}
const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => {
const sequelizeOptions = { transaction: t }
try {
const sequelizeOptions = { transaction: t }
const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight
videoCreated.VideoChannel = channel
const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight
videoCreated.VideoChannel = channel
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
const previewIcon = getPreviewFromIcons(videoObject)
const previewUrl = getPreviewUrl(previewIcon, videoCreated)
const previewModel = createPlaceholderThumbnail(previewUrl, videoCreated, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
// Process files
const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoObject.url)
const videoFilePromises = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t }))
const videoFiles = await Promise.all(videoFilePromises)
const streamingPlaylistsAttributes = streamingPlaylistActivityUrlToDBAttributes(videoCreated, videoObject, videoFiles)
videoCreated.VideoStreamingPlaylists = []
for (const playlistAttributes of streamingPlaylistsAttributes) {
const playlistModel = await VideoStreamingPlaylistModel.create(playlistAttributes, { transaction: t })
const playlistFiles = videoFileActivityUrlToDBAttributes(playlistModel, playlistAttributes.tagAPObject)
const videoFilePromises = playlistFiles.map(f => VideoFileModel.create(f, { transaction: t }))
playlistModel.VideoFiles = await Promise.all(videoFilePromises)
videoCreated.VideoStreamingPlaylists.push(playlistModel)
}
// Process tags
const tags = videoObject.tag
.filter(isAPHashTagObject)
.map(t => t.name)
await setVideoTags({ video: videoCreated, tags, transaction: t })
// Process captions
const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
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)
videoCreated.VideoFiles = videoFiles
if (videoCreated.isLive) {
const videoLive = new VideoLiveModel({
streamKey: null,
saveReplay: videoObject.liveSaveReplay,
permanentLive: videoObject.permanentLive,
videoId: videoCreated.id
const previewUrl = getPreviewUrl(getPreviewFromIcons(videoObject), videoCreated)
const previewModel = createPlaceholderThumbnail({
fileUrl: previewUrl,
video: videoCreated,
type: ThumbnailType.PREVIEW,
size: PREVIEWS_SIZE
})
videoCreated.VideoLive = await videoLive.save({ transaction: t })
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
// Process files
const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoObject.url)
const videoFilePromises = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t }))
const videoFiles = await Promise.all(videoFilePromises)
const streamingPlaylistsAttributes = streamingPlaylistActivityUrlToDBAttributes(videoCreated, videoObject, videoFiles)
videoCreated.VideoStreamingPlaylists = []
for (const playlistAttributes of streamingPlaylistsAttributes) {
const playlistModel = await VideoStreamingPlaylistModel.create(playlistAttributes, { transaction: t })
const playlistFiles = videoFileActivityUrlToDBAttributes(playlistModel, playlistAttributes.tagAPObject)
const videoFilePromises = playlistFiles.map(f => VideoFileModel.create(f, { transaction: t }))
playlistModel.VideoFiles = await Promise.all(videoFilePromises)
videoCreated.VideoStreamingPlaylists.push(playlistModel)
}
// Process tags
const tags = videoObject.tag
.filter(isAPHashTagObject)
.map(t => t.name)
await setVideoTags({ video: videoCreated, tags, transaction: t })
// Process captions
const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => {
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)
videoCreated.VideoFiles = videoFiles
if (videoCreated.isLive) {
const videoLive = new VideoLiveModel({
streamKey: null,
saveReplay: videoObject.liveSaveReplay,
permanentLive: videoObject.permanentLive,
videoId: videoCreated.id
})
videoCreated.VideoLive = await videoLive.save({ transaction: t })
}
const autoBlacklisted = await autoBlacklistVideoIfNeeded({
video: videoCreated,
user: undefined,
isRemote: true,
isNew: true,
transaction: t
})
logger.info('Remote video with uuid %s inserted.', videoObject.uuid)
return { autoBlacklisted, videoCreated }
} catch (err) {
// FIXME: Use rollback hook when https://github.com/sequelize/sequelize/pull/13038 is released
// Remove thumbnail
if (thumbnailModel) await thumbnailModel.removeThumbnail()
throw err
}
const autoBlacklisted = await autoBlacklistVideoIfNeeded({
video: videoCreated,
user: undefined,
isRemote: true,
isNew: true,
transaction: t
})
logger.info('Remote video with uuid %s inserted.', videoObject.uuid)
return { autoBlacklisted, videoCreated }
})
if (waitThumbnail === false) {

View File

@ -20,7 +20,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
}
async getFilePathImpl (filename: string) {
const thumbnail = await ThumbnailModel.loadWithVideoByName(filename, ThumbnailType.PREVIEW)
const thumbnail = await ThumbnailModel.loadWithVideoByFilename(filename, ThumbnailType.PREVIEW)
if (!thumbnail) return undefined
if (thumbnail.Video.isOwned()) return { isOwned: true, path: thumbnail.getPath() }

View File

@ -162,7 +162,11 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
let thumbnailModel: MThumbnail
let thumbnailSave: object
if (!videoImportWithFiles.Video.getMiniature()) {
thumbnailModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.MINIATURE)
thumbnailModel = await generateVideoMiniature({
video: videoImportWithFiles.Video,
videoFile,
type: ThumbnailType.MINIATURE
})
thumbnailSave = thumbnailModel.toJSON()
}
@ -170,7 +174,11 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
let previewModel: MThumbnail
let previewSave: object
if (!videoImportWithFiles.Video.getPreview()) {
previewModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.PREVIEW)
previewModel = await generateVideoMiniature({
video: videoImportWithFiles.Video,
videoFile,
type: ThumbnailType.PREVIEW
})
previewSave = previewModel.toJSON()
}

View File

@ -122,11 +122,19 @@ async function saveLive (video: MVideo, live: MVideoLive) {
// Regenerate the thumbnail & preview?
if (videoWithFiles.getMiniature().automaticallyGenerated === true) {
await generateVideoMiniature(videoWithFiles, videoWithFiles.getMaxQualityFile(), ThumbnailType.MINIATURE)
await generateVideoMiniature({
video: videoWithFiles,
videoFile: videoWithFiles.getMaxQualityFile(),
type: ThumbnailType.MINIATURE
})
}
if (videoWithFiles.getPreview().automaticallyGenerated === true) {
await generateVideoMiniature(videoWithFiles, videoWithFiles.getMaxQualityFile(), ThumbnailType.PREVIEW)
await generateVideoMiniature({
video: videoWithFiles,
videoFile: videoWithFiles.getMaxQualityFile(),
type: ThumbnailType.PREVIEW
})
}
await publishAndFederateIfNeeded(videoWithFiles, true)

View File

@ -1,33 +1,48 @@
import { join } from 'path'
import { ThumbnailType } from '../../shared/models/videos/thumbnail.type'
import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils'
import { processImage } from '../helpers/image-utils'
import { downloadImage } from '../helpers/requests'
import { CONFIG } from '../initializers/config'
import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants'
import { ThumbnailModel } from '../models/video/thumbnail'
import { ThumbnailType } from '../../shared/models/videos/thumbnail.type'
import { processImage } from '../helpers/image-utils'
import { join } from 'path'
import { downloadImage } from '../helpers/requests'
import { MVideoPlaylistThumbnail } from '../types/models/video/video-playlist'
import { MVideoFile, MVideoThumbnail } from '../types/models'
import { MThumbnail } from '../types/models/video/thumbnail'
import { MVideoPlaylistThumbnail } from '../types/models/video/video-playlist'
import { getVideoFilePath } from './video-paths'
type ImageSize = { height: number, width: number }
function createPlaylistMiniatureFromExisting (
inputPath: string,
playlist: MVideoPlaylistThumbnail,
automaticallyGenerated: boolean,
keepOriginal = false,
function createPlaylistMiniatureFromExisting (options: {
inputPath: string
playlist: MVideoPlaylistThumbnail
automaticallyGenerated: boolean
keepOriginal?: boolean // default to false
size?: ImageSize
) {
}) {
const { inputPath, playlist, automaticallyGenerated, keepOriginal = false, size } = options
const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
const type = ThumbnailType.MINIATURE
const thumbnailCreator = () => processImage(inputPath, outputPath, { width, height }, keepOriginal)
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail })
return createThumbnailFromFunction({
thumbnailCreator,
filename,
height,
width,
type,
automaticallyGenerated,
existingThumbnail
})
}
function createPlaylistMiniatureFromUrl (downloadUrl: string, playlist: MVideoPlaylistThumbnail, size?: ImageSize) {
function createPlaylistMiniatureFromUrl (options: {
downloadUrl: string
playlist: MVideoPlaylistThumbnail
size?: ImageSize
}) {
const { downloadUrl, playlist, size } = options
const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
const type = ThumbnailType.MINIATURE
@ -40,7 +55,13 @@ function createPlaylistMiniatureFromUrl (downloadUrl: string, playlist: MVideoPl
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl })
}
function createVideoMiniatureFromUrl (downloadUrl: string, video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) {
function createVideoMiniatureFromUrl (options: {
downloadUrl: string
video: MVideoThumbnail
type: ThumbnailType
size?: ImageSize
}) {
const { downloadUrl, video, type, size } = options
const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
// Only save the file URL if it is a remote video
@ -58,17 +79,31 @@ function createVideoMiniatureFromExisting (options: {
type: ThumbnailType
automaticallyGenerated: boolean
size?: ImageSize
keepOriginal?: boolean
keepOriginal?: boolean // default to false
}) {
const { inputPath, video, type, automaticallyGenerated, size, keepOriginal } = options
const { inputPath, video, type, automaticallyGenerated, size, keepOriginal = false } = options
const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
const thumbnailCreator = () => processImage(inputPath, outputPath, { width, height }, keepOriginal)
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail })
return createThumbnailFromFunction({
thumbnailCreator,
filename,
height,
width,
type,
automaticallyGenerated,
existingThumbnail
})
}
function generateVideoMiniature (video: MVideoThumbnail, videoFile: MVideoFile, type: ThumbnailType) {
function generateVideoMiniature (options: {
video: MVideoThumbnail
videoFile: MVideoFile
type: ThumbnailType
}) {
const { video, videoFile, type } = options
const input = getVideoFilePath(video, videoFile)
const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type)
@ -76,10 +111,24 @@ function generateVideoMiniature (video: MVideoThumbnail, videoFile: MVideoFile,
? () => processImage(ASSETS_PATH.DEFAULT_AUDIO_BACKGROUND, outputPath, { width, height }, true)
: () => generateImageFromVideoFile(input, basePath, filename, { height, width })
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated: true, existingThumbnail })
return createThumbnailFromFunction({
thumbnailCreator,
filename,
height,
width,
type,
automaticallyGenerated: true,
existingThumbnail
})
}
function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) {
function createPlaceholderThumbnail (options: {
fileUrl: string
video: MVideoThumbnail
type: ThumbnailType
size: ImageSize
}) {
const { fileUrl, video, type, size } = options
const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
const thumbnail = existingThumbnail || new ThumbnailModel()
@ -164,12 +213,22 @@ async function createThumbnailFromFunction (parameters: {
fileUrl?: string
existingThumbnail?: MThumbnail
}) {
const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters
const {
thumbnailCreator,
filename,
width,
height,
type,
existingThumbnail,
automaticallyGenerated = null,
fileUrl = null
} = parameters
// Remove old file
if (existingThumbnail) await existingThumbnail.removeThumbnail()
const oldFilename = existingThumbnail
? existingThumbnail.filename
: undefined
const thumbnail = existingThumbnail || new ThumbnailModel()
const thumbnail: MThumbnail = existingThumbnail || new ThumbnailModel()
thumbnail.filename = filename
thumbnail.height = height
@ -177,6 +236,7 @@ async function createThumbnailFromFunction (parameters: {
thumbnail.type = type
thumbnail.fileUrl = fileUrl
thumbnail.automaticallyGenerated = automaticallyGenerated
thumbnail.previousThumbnailFilename = oldFilename
await thumbnailCreator()

View File

@ -3,6 +3,8 @@ import { join } from 'path'
import {
AfterDestroy,
AllowNull,
BeforeCreate,
BeforeUpdate,
BelongsTo,
Column,
CreatedAt,
@ -14,7 +16,8 @@ import {
UpdatedAt
} from 'sequelize-typescript'
import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
import { MThumbnailVideo, MVideoAccountLight } from '@server/types/models'
import { afterCommitIfTransaction } from '@server/helpers/database-utils'
import { MThumbnail, MThumbnailVideo, MVideoAccountLight } from '@server/types/models'
import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
import { logger } from '../../helpers/logger'
import { CONFIG } from '../../initializers/config'
@ -96,6 +99,9 @@ export class ThumbnailModel extends Model {
@UpdatedAt
updatedAt: Date
// If this thumbnail replaced existing one, track the old name
previousThumbnailFilename: string
private static readonly types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = {
[ThumbnailType.MINIATURE]: {
label: 'miniature',
@ -109,6 +115,12 @@ export class ThumbnailModel extends Model {
}
}
@BeforeCreate
@BeforeUpdate
static removeOldFile (instance: ThumbnailModel, options) {
return afterCommitIfTransaction(options.transaction, () => instance.removePreviousFilenameIfNeeded())
}
@AfterDestroy
static removeFiles (instance: ThumbnailModel) {
logger.info('Removing %s file %s.', ThumbnailModel.types[instance.type].label, instance.filename)
@ -118,7 +130,18 @@ export class ThumbnailModel extends Model {
.catch(err => logger.error('Cannot remove thumbnail file %s.', instance.filename, err))
}
static loadWithVideoByName (filename: string, thumbnailType: ThumbnailType): Promise<MThumbnailVideo> {
static loadByFilename (filename: string, thumbnailType: ThumbnailType): Promise<MThumbnail> {
const query = {
where: {
filename,
type: thumbnailType
}
}
return ThumbnailModel.findOne(query)
}
static loadWithVideoByFilename (filename: string, thumbnailType: ThumbnailType): Promise<MThumbnailVideo> {
const query = {
where: {
filename,
@ -150,7 +173,22 @@ export class ThumbnailModel extends Model {
return join(directory, this.filename)
}
getPreviousPath () {
const directory = ThumbnailModel.types[this.type].directory
return join(directory, this.previousThumbnailFilename)
}
removeThumbnail () {
return remove(this.getPath())
}
removePreviousFilenameIfNeeded () {
if (!this.previousThumbnailFilename) return
const previousPath = this.getPreviousPath()
remove(previousPath)
.catch(err => logger.error('Cannot remove previous thumbnail file %s.', previousPath, { err }))
this.previousThumbnailFilename = undefined
}
}

View File

@ -17,6 +17,7 @@ import {
Table,
UpdatedAt
} from 'sequelize-typescript'
import { v4 as uuidv4 } from 'uuid'
import { MAccountId, MChannelId } from '@server/types/models'
import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'

View File

@ -558,7 +558,7 @@ describe('Test follows', 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/' + video4.uuid + '-ar.vtt')
expect(caption1.captionPath).to.match(new RegExp('^/lazy-static/video-captions/.+-ar.vtt$'))
await testCaptionFile(servers[0].url, caption1.captionPath, 'Subtitle good 2.')
})

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 { Video, VideoChannel } from '../../models/videos'
import { VideoChannel } from '../../models/videos'
import { buildServerDirectory, getFileSize, isGithubCI, root, wait } from '../miscs/miscs'
import { makeGetRequest } from '../requests/requests'