Remove previous thumbnail if needed
This commit is contained in:
parent
6302d599cd
commit
a35a22797c
15 changed files with 274 additions and 119 deletions
|
@ -23,6 +23,7 @@
|
||||||
"consistent-as-needed"
|
"consistent-as-needed"
|
||||||
],
|
],
|
||||||
"padded-blocks": "off",
|
"padded-blocks": "off",
|
||||||
|
"prefer-regex-literals": "off",
|
||||||
"no-async-promise-executor": "off",
|
"no-async-promise-executor": "off",
|
||||||
"dot-notation": "off",
|
"dot-notation": "off",
|
||||||
"promise/param-names": "off",
|
"promise/param-names": "off",
|
||||||
|
|
|
@ -95,7 +95,7 @@ function doesVideoExist (keepOnlyOwned: boolean) {
|
||||||
|
|
||||||
function doesThumbnailExist (keepOnlyOwned: boolean, type: ThumbnailType) {
|
function doesThumbnailExist (keepOnlyOwned: boolean, type: ThumbnailType) {
|
||||||
return async (file: string) => {
|
return async (file: string) => {
|
||||||
const thumbnail = await ThumbnailModel.loadWithVideoByName(file, type)
|
const thumbnail = await ThumbnailModel.loadByFilename(file, type)
|
||||||
if (!thumbnail) return false
|
if (!thumbnail) return false
|
||||||
|
|
||||||
if (keepOnlyOwned) {
|
if (keepOnlyOwned) {
|
||||||
|
|
|
@ -173,7 +173,11 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
|
||||||
|
|
||||||
const thumbnailField = req.files['thumbnailfile']
|
const thumbnailField = req.files['thumbnailfile']
|
||||||
const thumbnailModel = thumbnailField
|
const thumbnailModel = thumbnailField
|
||||||
? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist, false)
|
? await createPlaylistMiniatureFromExisting({
|
||||||
|
inputPath: thumbnailField[0].path,
|
||||||
|
playlist: videoPlaylist,
|
||||||
|
automaticallyGenerated: false
|
||||||
|
})
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
const videoPlaylistCreated = await sequelizeTypescript.transaction(async t => {
|
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 thumbnailField = req.files['thumbnailfile']
|
||||||
const thumbnailModel = thumbnailField
|
const thumbnailModel = thumbnailField
|
||||||
? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylistInstance, false)
|
? await createPlaylistMiniatureFromExisting({
|
||||||
|
inputPath: thumbnailField[0].path,
|
||||||
|
playlist: videoPlaylistInstance,
|
||||||
|
automaticallyGenerated: false
|
||||||
|
})
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -474,7 +482,12 @@ async function generateThumbnailForPlaylist (videoPlaylist: MVideoPlaylistThumbn
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, videoMiniature.filename)
|
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
|
thumbnailModel.videoPlaylistId = videoPlaylist.id
|
||||||
|
|
||||||
|
|
|
@ -282,7 +282,7 @@ async function processPreview (req: express.Request, video: MVideoThumbnail): Pr
|
||||||
|
|
||||||
async function processThumbnailFromUrl (url: string, video: MVideoThumbnail) {
|
async function processThumbnailFromUrl (url: string, video: MVideoThumbnail) {
|
||||||
try {
|
try {
|
||||||
return createVideoMiniatureFromUrl(url, video, ThumbnailType.MINIATURE)
|
return createVideoMiniatureFromUrl({ downloadUrl: url, video, type: ThumbnailType.MINIATURE })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn('Cannot generate video thumbnail %s for %s.', url, video.url, { err })
|
logger.warn('Cannot generate video thumbnail %s for %s.', url, video.url, { err })
|
||||||
return undefined
|
return undefined
|
||||||
|
@ -291,14 +291,14 @@ async function processThumbnailFromUrl (url: string, video: MVideoThumbnail) {
|
||||||
|
|
||||||
async function processPreviewFromUrl (url: string, video: MVideoThumbnail) {
|
async function processPreviewFromUrl (url: string, video: MVideoThumbnail) {
|
||||||
try {
|
try {
|
||||||
return createVideoMiniatureFromUrl(url, video, ThumbnailType.PREVIEW)
|
return createVideoMiniatureFromUrl({ downloadUrl: url, video, type: ThumbnailType.PREVIEW })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn('Cannot generate video preview %s for %s.', url, video.url, { err })
|
logger.warn('Cannot generate video preview %s for %s.', url, video.url, { err })
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertIntoDB (parameters: {
|
async function insertIntoDB (parameters: {
|
||||||
video: MVideoThumbnail
|
video: MVideoThumbnail
|
||||||
thumbnailModel: MThumbnail
|
thumbnailModel: MThumbnail
|
||||||
previewModel: MThumbnail
|
previewModel: MThumbnail
|
||||||
|
@ -309,7 +309,7 @@ function insertIntoDB (parameters: {
|
||||||
}): Promise<MVideoImportFormattable> {
|
}): Promise<MVideoImportFormattable> {
|
||||||
const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters
|
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 }
|
const sequelizeOptions = { transaction: t }
|
||||||
|
|
||||||
// Save video object in database
|
// Save video object in database
|
||||||
|
@ -339,4 +339,6 @@ function insertIntoDB (parameters: {
|
||||||
|
|
||||||
return videoImport
|
return videoImport
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return videoImport
|
||||||
}
|
}
|
||||||
|
|
|
@ -215,7 +215,7 @@ async function addVideo (req: express.Request, res: express.Response) {
|
||||||
const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({
|
const [ thumbnailModel, previewModel ] = await buildVideoThumbnailsFromReq({
|
||||||
video,
|
video,
|
||||||
files: req.files,
|
files: req.files,
|
||||||
fallback: type => generateVideoMiniature(video, videoFile, type)
|
fallback: type => generateVideoMiniature({ video, videoFile, type })
|
||||||
})
|
})
|
||||||
|
|
||||||
// Create the torrent file
|
// Create the torrent file
|
||||||
|
|
|
@ -103,7 +103,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
|
||||||
|
|
||||||
if (playlistObject.icon) {
|
if (playlistObject.icon) {
|
||||||
try {
|
try {
|
||||||
const thumbnailModel = await createPlaylistMiniatureFromUrl(playlistObject.icon.url, refreshedPlaylist)
|
const thumbnailModel = await createPlaylistMiniatureFromUrl({ downloadUrl: playlistObject.icon.url, playlist: refreshedPlaylist })
|
||||||
await refreshedPlaylist.setAndSaveThumbnail(thumbnailModel, undefined)
|
await refreshedPlaylist.setAndSaveThumbnail(thumbnailModel, undefined)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn('Cannot generate thumbnail of %s.', playlistObject.id, { err })
|
logger.warn('Cannot generate thumbnail of %s.', playlistObject.id, { err })
|
||||||
|
|
|
@ -313,7 +313,11 @@ async function updateVideoFromAP (options: {
|
||||||
let thumbnailModel: MThumbnail
|
let thumbnailModel: MThumbnail
|
||||||
|
|
||||||
try {
|
try {
|
||||||
thumbnailModel = await createVideoMiniatureFromUrl(getThumbnailFromIcons(videoObject).url, video, ThumbnailType.MINIATURE)
|
thumbnailModel = await createVideoMiniatureFromUrl({
|
||||||
|
downloadUrl: getThumbnailFromIcons(videoObject).url,
|
||||||
|
video,
|
||||||
|
type: ThumbnailType.MINIATURE
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err })
|
logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err })
|
||||||
}
|
}
|
||||||
|
@ -362,7 +366,12 @@ async function updateVideoFromAP (options: {
|
||||||
|
|
||||||
if (videoUpdated.getPreview()) {
|
if (videoUpdated.getPreview()) {
|
||||||
const previewUrl = getPreviewUrl(getPreviewFromIcons(videoObject), video)
|
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)
|
await videoUpdated.addAndSaveThumbnail(previewModel, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -585,8 +594,11 @@ async function createVideo (videoObject: VideoObject, channel: MChannelAccountLi
|
||||||
const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to)
|
const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to)
|
||||||
const video = VideoModel.build(videoData) as MVideoThumbnail
|
const video = VideoModel.build(videoData) as MVideoThumbnail
|
||||||
|
|
||||||
const promiseThumbnail = createVideoMiniatureFromUrl(getThumbnailFromIcons(videoObject).url, video, ThumbnailType.MINIATURE)
|
const promiseThumbnail = createVideoMiniatureFromUrl({
|
||||||
.catch(err => {
|
downloadUrl: getThumbnailFromIcons(videoObject).url,
|
||||||
|
video,
|
||||||
|
type: ThumbnailType.MINIATURE
|
||||||
|
}).catch(err => {
|
||||||
logger.error('Cannot create miniature from url.', { err })
|
logger.error('Cannot create miniature from url.', { err })
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
|
@ -597,6 +609,7 @@ async function createVideo (videoObject: VideoObject, channel: MChannelAccountLi
|
||||||
}
|
}
|
||||||
|
|
||||||
const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => {
|
const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => {
|
||||||
|
try {
|
||||||
const sequelizeOptions = { transaction: t }
|
const sequelizeOptions = { transaction: t }
|
||||||
|
|
||||||
const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight
|
const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight
|
||||||
|
@ -604,9 +617,13 @@ async function createVideo (videoObject: VideoObject, channel: MChannelAccountLi
|
||||||
|
|
||||||
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
|
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
|
||||||
|
|
||||||
const previewIcon = getPreviewFromIcons(videoObject)
|
const previewUrl = getPreviewUrl(getPreviewFromIcons(videoObject), videoCreated)
|
||||||
const previewUrl = getPreviewUrl(previewIcon, videoCreated)
|
const previewModel = createPlaceholderThumbnail({
|
||||||
const previewModel = createPlaceholderThumbnail(previewUrl, videoCreated, ThumbnailType.PREVIEW, PREVIEWS_SIZE)
|
fileUrl: previewUrl,
|
||||||
|
video: videoCreated,
|
||||||
|
type: ThumbnailType.PREVIEW,
|
||||||
|
size: PREVIEWS_SIZE
|
||||||
|
})
|
||||||
|
|
||||||
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
|
if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
|
||||||
|
|
||||||
|
@ -672,6 +689,13 @@ async function createVideo (videoObject: VideoObject, channel: MChannelAccountLi
|
||||||
logger.info('Remote video with uuid %s inserted.', videoObject.uuid)
|
logger.info('Remote video with uuid %s inserted.', videoObject.uuid)
|
||||||
|
|
||||||
return { autoBlacklisted, videoCreated }
|
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
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if (waitThumbnail === false) {
|
if (waitThumbnail === false) {
|
||||||
|
|
|
@ -20,7 +20,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFilePathImpl (filename: 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) return undefined
|
||||||
|
|
||||||
if (thumbnail.Video.isOwned()) return { isOwned: true, path: thumbnail.getPath() }
|
if (thumbnail.Video.isOwned()) return { isOwned: true, path: thumbnail.getPath() }
|
||||||
|
|
|
@ -162,7 +162,11 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
|
||||||
let thumbnailModel: MThumbnail
|
let thumbnailModel: MThumbnail
|
||||||
let thumbnailSave: object
|
let thumbnailSave: object
|
||||||
if (!videoImportWithFiles.Video.getMiniature()) {
|
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()
|
thumbnailSave = thumbnailModel.toJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +174,11 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid
|
||||||
let previewModel: MThumbnail
|
let previewModel: MThumbnail
|
||||||
let previewSave: object
|
let previewSave: object
|
||||||
if (!videoImportWithFiles.Video.getPreview()) {
|
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()
|
previewSave = previewModel.toJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -122,11 +122,19 @@ async function saveLive (video: MVideo, live: MVideoLive) {
|
||||||
|
|
||||||
// Regenerate the thumbnail & preview?
|
// Regenerate the thumbnail & preview?
|
||||||
if (videoWithFiles.getMiniature().automaticallyGenerated === true) {
|
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) {
|
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)
|
await publishAndFederateIfNeeded(videoWithFiles, true)
|
||||||
|
|
|
@ -1,33 +1,48 @@
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
|
import { ThumbnailType } from '../../shared/models/videos/thumbnail.type'
|
||||||
import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils'
|
import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils'
|
||||||
|
import { processImage } from '../helpers/image-utils'
|
||||||
|
import { downloadImage } from '../helpers/requests'
|
||||||
import { CONFIG } from '../initializers/config'
|
import { CONFIG } from '../initializers/config'
|
||||||
import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants'
|
import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants'
|
||||||
import { ThumbnailModel } from '../models/video/thumbnail'
|
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 { MVideoFile, MVideoThumbnail } from '../types/models'
|
||||||
import { MThumbnail } from '../types/models/video/thumbnail'
|
import { MThumbnail } from '../types/models/video/thumbnail'
|
||||||
|
import { MVideoPlaylistThumbnail } from '../types/models/video/video-playlist'
|
||||||
import { getVideoFilePath } from './video-paths'
|
import { getVideoFilePath } from './video-paths'
|
||||||
|
|
||||||
type ImageSize = { height: number, width: number }
|
type ImageSize = { height: number, width: number }
|
||||||
|
|
||||||
function createPlaylistMiniatureFromExisting (
|
function createPlaylistMiniatureFromExisting (options: {
|
||||||
inputPath: string,
|
inputPath: string
|
||||||
playlist: MVideoPlaylistThumbnail,
|
playlist: MVideoPlaylistThumbnail
|
||||||
automaticallyGenerated: boolean,
|
automaticallyGenerated: boolean
|
||||||
keepOriginal = false,
|
keepOriginal?: boolean // default to false
|
||||||
size?: ImageSize
|
size?: ImageSize
|
||||||
) {
|
}) {
|
||||||
|
const { inputPath, playlist, automaticallyGenerated, keepOriginal = false, size } = options
|
||||||
const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
|
const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
|
||||||
const type = ThumbnailType.MINIATURE
|
const type = ThumbnailType.MINIATURE
|
||||||
|
|
||||||
const thumbnailCreator = () => processImage(inputPath, outputPath, { width, height }, keepOriginal)
|
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 { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size)
|
||||||
const type = ThumbnailType.MINIATURE
|
const type = ThumbnailType.MINIATURE
|
||||||
|
|
||||||
|
@ -40,7 +55,13 @@ function createPlaylistMiniatureFromUrl (downloadUrl: string, playlist: MVideoPl
|
||||||
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl })
|
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)
|
const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
|
||||||
|
|
||||||
// Only save the file URL if it is a remote video
|
// Only save the file URL if it is a remote video
|
||||||
|
@ -58,17 +79,31 @@ function createVideoMiniatureFromExisting (options: {
|
||||||
type: ThumbnailType
|
type: ThumbnailType
|
||||||
automaticallyGenerated: boolean
|
automaticallyGenerated: boolean
|
||||||
size?: ImageSize
|
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 { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
|
||||||
const thumbnailCreator = () => processImage(inputPath, outputPath, { width, height }, keepOriginal)
|
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 input = getVideoFilePath(video, videoFile)
|
||||||
|
|
||||||
const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type)
|
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)
|
? () => processImage(ASSETS_PATH.DEFAULT_AUDIO_BACKGROUND, outputPath, { width, height }, true)
|
||||||
: () => generateImageFromVideoFile(input, basePath, filename, { height, width })
|
: () => 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 { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
|
||||||
|
|
||||||
const thumbnail = existingThumbnail || new ThumbnailModel()
|
const thumbnail = existingThumbnail || new ThumbnailModel()
|
||||||
|
@ -164,12 +213,22 @@ async function createThumbnailFromFunction (parameters: {
|
||||||
fileUrl?: string
|
fileUrl?: string
|
||||||
existingThumbnail?: MThumbnail
|
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
|
const oldFilename = existingThumbnail
|
||||||
if (existingThumbnail) await existingThumbnail.removeThumbnail()
|
? existingThumbnail.filename
|
||||||
|
: undefined
|
||||||
|
|
||||||
const thumbnail = existingThumbnail || new ThumbnailModel()
|
const thumbnail: MThumbnail = existingThumbnail || new ThumbnailModel()
|
||||||
|
|
||||||
thumbnail.filename = filename
|
thumbnail.filename = filename
|
||||||
thumbnail.height = height
|
thumbnail.height = height
|
||||||
|
@ -177,6 +236,7 @@ async function createThumbnailFromFunction (parameters: {
|
||||||
thumbnail.type = type
|
thumbnail.type = type
|
||||||
thumbnail.fileUrl = fileUrl
|
thumbnail.fileUrl = fileUrl
|
||||||
thumbnail.automaticallyGenerated = automaticallyGenerated
|
thumbnail.automaticallyGenerated = automaticallyGenerated
|
||||||
|
thumbnail.previousThumbnailFilename = oldFilename
|
||||||
|
|
||||||
await thumbnailCreator()
|
await thumbnailCreator()
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { join } from 'path'
|
||||||
import {
|
import {
|
||||||
AfterDestroy,
|
AfterDestroy,
|
||||||
AllowNull,
|
AllowNull,
|
||||||
|
BeforeCreate,
|
||||||
|
BeforeUpdate,
|
||||||
BelongsTo,
|
BelongsTo,
|
||||||
Column,
|
Column,
|
||||||
CreatedAt,
|
CreatedAt,
|
||||||
|
@ -14,7 +16,8 @@ import {
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub'
|
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 { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
|
||||||
import { logger } from '../../helpers/logger'
|
import { logger } from '../../helpers/logger'
|
||||||
import { CONFIG } from '../../initializers/config'
|
import { CONFIG } from '../../initializers/config'
|
||||||
|
@ -96,6 +99,9 @@ export class ThumbnailModel extends Model {
|
||||||
@UpdatedAt
|
@UpdatedAt
|
||||||
updatedAt: Date
|
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 } } = {
|
private static readonly types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = {
|
||||||
[ThumbnailType.MINIATURE]: {
|
[ThumbnailType.MINIATURE]: {
|
||||||
label: '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
|
@AfterDestroy
|
||||||
static removeFiles (instance: ThumbnailModel) {
|
static removeFiles (instance: ThumbnailModel) {
|
||||||
logger.info('Removing %s file %s.', ThumbnailModel.types[instance.type].label, instance.filename)
|
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))
|
.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 = {
|
const query = {
|
||||||
where: {
|
where: {
|
||||||
filename,
|
filename,
|
||||||
|
@ -150,7 +173,22 @@ export class ThumbnailModel extends Model {
|
||||||
return join(directory, this.filename)
|
return join(directory, this.filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPreviousPath () {
|
||||||
|
const directory = ThumbnailModel.types[this.type].directory
|
||||||
|
return join(directory, this.previousThumbnailFilename)
|
||||||
|
}
|
||||||
|
|
||||||
removeThumbnail () {
|
removeThumbnail () {
|
||||||
return remove(this.getPath())
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
Table,
|
Table,
|
||||||
UpdatedAt
|
UpdatedAt
|
||||||
} from 'sequelize-typescript'
|
} from 'sequelize-typescript'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { MAccountId, MChannelId } from '@server/types/models'
|
import { MAccountId, MChannelId } from '@server/types/models'
|
||||||
import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
|
import { ActivityIconObject } from '../../../shared/models/activitypub/objects'
|
||||||
import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
|
import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object'
|
||||||
|
|
|
@ -558,7 +558,7 @@ describe('Test follows', function () {
|
||||||
const caption1: VideoCaption = res.body.data[0]
|
const caption1: VideoCaption = res.body.data[0]
|
||||||
expect(caption1.language.id).to.equal('ar')
|
expect(caption1.language.id).to.equal('ar')
|
||||||
expect(caption1.language.label).to.equal('Arabic')
|
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.')
|
await testCaptionFile(servers[0].url, caption1.captionPath, 'Subtitle good 2.')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { ChildProcess, exec, fork } from 'child_process'
|
||||||
import { copy, ensureDir, pathExists, readdir, readFile, remove } from 'fs-extra'
|
import { copy, ensureDir, pathExists, readdir, readFile, remove } from 'fs-extra'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { randomInt } from '../../core-utils/miscs/miscs'
|
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 { buildServerDirectory, getFileSize, isGithubCI, root, wait } from '../miscs/miscs'
|
||||||
import { makeGetRequest } from '../requests/requests'
|
import { makeGetRequest } from '../requests/requests'
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue