Automatically update playlist thumbnails
This commit is contained in:
parent
a21e25ff64
commit
65af03a241
15 changed files with 443 additions and 43 deletions
|
@ -63,24 +63,26 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro
|
|||
|
||||
if (oldPosition > insertAfter) insertAfter--
|
||||
|
||||
this.videoPlaylistService.reorderPlaylist(this.playlist.id, oldPosition, insertAfter)
|
||||
.subscribe(
|
||||
() => { /* nothing to do */ },
|
||||
|
||||
err => this.notifier.error(err.message)
|
||||
)
|
||||
|
||||
const element = this.playlistElements[previousIndex]
|
||||
|
||||
this.playlistElements.splice(previousIndex, 1)
|
||||
this.playlistElements.splice(newIndex, 0, element)
|
||||
|
||||
this.videoPlaylistService.reorderPlaylist(this.playlist.id, oldPosition, insertAfter)
|
||||
.subscribe(
|
||||
() => {
|
||||
this.reorderClientPositions()
|
||||
},
|
||||
|
||||
err => this.notifier.error(err.message)
|
||||
)
|
||||
}
|
||||
|
||||
onElementRemoved (element: VideoPlaylistElement) {
|
||||
const oldFirst = this.findFirst()
|
||||
|
||||
this.playlistElements = this.playlistElements.filter(v => v.id !== element.id)
|
||||
this.reorderClientPositions()
|
||||
this.reorderClientPositions(oldFirst)
|
||||
}
|
||||
|
||||
onNearOfBottom () {
|
||||
|
@ -110,12 +112,25 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro
|
|||
})
|
||||
}
|
||||
|
||||
private reorderClientPositions () {
|
||||
private reorderClientPositions (first?: VideoPlaylistElement) {
|
||||
if (this.playlistElements.length === 0) return
|
||||
|
||||
const oldFirst = first || this.findFirst()
|
||||
let i = 1
|
||||
|
||||
for (const element of this.playlistElements) {
|
||||
element.position = i
|
||||
i++
|
||||
}
|
||||
|
||||
// Reload playlist thumbnail if the first element changed
|
||||
const newFirst = this.findFirst()
|
||||
if (oldFirst && newFirst && oldFirst.id !== newFirst.id) {
|
||||
this.playlist.refreshThumbnail()
|
||||
}
|
||||
}
|
||||
|
||||
private findFirst () {
|
||||
return this.playlistElements.find(e => e.position === 1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,9 @@ export class VideoPlaylist implements ServerVideoPlaylist {
|
|||
videoChannelBy?: string
|
||||
videoChannelAvatarUrl?: string
|
||||
|
||||
private thumbnailVersion: number
|
||||
private originThumbnailUrl: string
|
||||
|
||||
constructor (hash: ServerVideoPlaylist, translations: {}) {
|
||||
const absoluteAPIUrl = getAbsoluteAPIUrl()
|
||||
|
||||
|
@ -54,6 +57,7 @@ export class VideoPlaylist implements ServerVideoPlaylist {
|
|||
|
||||
if (this.thumbnailPath) {
|
||||
this.thumbnailUrl = absoluteAPIUrl + hash.thumbnailPath
|
||||
this.originThumbnailUrl = this.thumbnailUrl
|
||||
} else {
|
||||
this.thumbnailUrl = window.location.origin + '/client/assets/images/default-playlist.jpg'
|
||||
}
|
||||
|
@ -81,4 +85,13 @@ export class VideoPlaylist implements ServerVideoPlaylist {
|
|||
this.displayName = peertubeTranslate(this.displayName, translations)
|
||||
}
|
||||
}
|
||||
|
||||
refreshThumbnail () {
|
||||
if (!this.originThumbnailUrl) return
|
||||
|
||||
if (!this.thumbnailVersion) this.thumbnailVersion = 0
|
||||
this.thumbnailVersion++
|
||||
|
||||
this.thumbnailUrl = this.originThumbnailUrl + '?v' + this.thumbnailVersion
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ import { JobQueue } from '../../lib/job-queue'
|
|||
import { CONFIG } from '../../initializers/config'
|
||||
import { sequelizeTypescript } from '../../initializers/database'
|
||||
import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail'
|
||||
import { VideoModel } from '../../models/video/video'
|
||||
|
||||
const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR })
|
||||
|
||||
|
@ -171,13 +172,16 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) {
|
|||
|
||||
const thumbnailField = req.files['thumbnailfile']
|
||||
const thumbnailModel = thumbnailField
|
||||
? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist)
|
||||
? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist, false)
|
||||
: undefined
|
||||
|
||||
const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => {
|
||||
const videoPlaylistCreated = await videoPlaylist.save({ transaction: t })
|
||||
|
||||
if (thumbnailModel) await videoPlaylistCreated.setAndSaveThumbnail(thumbnailModel, t)
|
||||
if (thumbnailModel) {
|
||||
thumbnailModel.automaticallyGenerated = false
|
||||
await videoPlaylistCreated.setAndSaveThumbnail(thumbnailModel, t)
|
||||
}
|
||||
|
||||
// We need more attributes for the federation
|
||||
videoPlaylistCreated.OwnerAccount = await AccountModel.load(user.Account.id, t)
|
||||
|
@ -206,7 +210,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
|
|||
|
||||
const thumbnailField = req.files['thumbnailfile']
|
||||
const thumbnailModel = thumbnailField
|
||||
? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylistInstance)
|
||||
? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylistInstance, false)
|
||||
: undefined
|
||||
|
||||
try {
|
||||
|
@ -239,7 +243,10 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response)
|
|||
|
||||
const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions)
|
||||
|
||||
if (thumbnailModel) await playlistUpdated.setAndSaveThumbnail(thumbnailModel, t)
|
||||
if (thumbnailModel) {
|
||||
thumbnailModel.automaticallyGenerated = false
|
||||
await playlistUpdated.setAndSaveThumbnail(thumbnailModel, t)
|
||||
}
|
||||
|
||||
const isNewPlaylist = wasPrivatePlaylist && playlistUpdated.privacy !== VideoPlaylistPrivacy.PRIVATE
|
||||
|
||||
|
@ -301,23 +308,17 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response)
|
|||
videoPlaylist.changed('updatedAt', true)
|
||||
await videoPlaylist.save({ transaction: t })
|
||||
|
||||
await sendUpdateVideoPlaylist(videoPlaylist, t)
|
||||
|
||||
return playlistElement
|
||||
})
|
||||
|
||||
// If the user did not set a thumbnail, automatically take the video thumbnail
|
||||
if (videoPlaylist.hasThumbnail() === false) {
|
||||
logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url)
|
||||
|
||||
const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename)
|
||||
const thumbnailModel = await createPlaylistMiniatureFromExisting(inputPath, videoPlaylist, true)
|
||||
|
||||
thumbnailModel.videoPlaylistId = videoPlaylist.id
|
||||
|
||||
await thumbnailModel.save()
|
||||
if (videoPlaylist.hasThumbnail() === false || (videoPlaylist.hasGeneratedThumbnail() && playlistElement.position === 1)) {
|
||||
await generateThumbnailForPlaylist(videoPlaylist, video)
|
||||
}
|
||||
|
||||
sendUpdateVideoPlaylist(videoPlaylist, undefined)
|
||||
.catch(err => logger.error('Cannot send video playlist update.', { err }))
|
||||
|
||||
logger.info('Video added in playlist %s at position %d.', videoPlaylist.uuid, playlistElement.position)
|
||||
|
||||
return res.json({
|
||||
|
@ -365,11 +366,17 @@ async function removeVideoFromPlaylist (req: express.Request, res: express.Respo
|
|||
videoPlaylist.changed('updatedAt', true)
|
||||
await videoPlaylist.save({ transaction: t })
|
||||
|
||||
await sendUpdateVideoPlaylist(videoPlaylist, t)
|
||||
|
||||
logger.info('Video playlist element %d of playlist %s deleted.', videoPlaylistElement.position, videoPlaylist.uuid)
|
||||
})
|
||||
|
||||
// Do we need to regenerate the default thumbnail?
|
||||
if (positionToDelete === 1 && videoPlaylist.hasGeneratedThumbnail()) {
|
||||
await regeneratePlaylistThumbnail(videoPlaylist)
|
||||
}
|
||||
|
||||
sendUpdateVideoPlaylist(videoPlaylist, undefined)
|
||||
.catch(err => logger.error('Cannot send video playlist update.', { err }))
|
||||
|
||||
return res.type('json').status(204).end()
|
||||
}
|
||||
|
||||
|
@ -413,8 +420,13 @@ async function reorderVideosPlaylist (req: express.Request, res: express.Respons
|
|||
await sendUpdateVideoPlaylist(videoPlaylist, t)
|
||||
})
|
||||
|
||||
// The first element changed
|
||||
if ((start === 1 || insertAfter === 0) && videoPlaylist.hasGeneratedThumbnail()) {
|
||||
await regeneratePlaylistThumbnail(videoPlaylist)
|
||||
}
|
||||
|
||||
logger.info(
|
||||
'Reordered playlist %s (inserted after %d elements %d - %d).',
|
||||
'Reordered playlist %s (inserted after position %d elements %d - %d).',
|
||||
videoPlaylist.uuid, insertAfter, start, start + reorderLength - 1
|
||||
)
|
||||
|
||||
|
@ -440,3 +452,22 @@ async function getVideoPlaylistVideos (req: express.Request, res: express.Respon
|
|||
}
|
||||
return res.json(getFormattedObjects(resultList.data, resultList.total, options))
|
||||
}
|
||||
|
||||
async function regeneratePlaylistThumbnail (videoPlaylist: VideoPlaylistModel) {
|
||||
await videoPlaylist.Thumbnail.destroy()
|
||||
videoPlaylist.Thumbnail = null
|
||||
|
||||
const firstElement = await VideoPlaylistElementModel.loadFirstElementWithVideoThumbnail(videoPlaylist.id)
|
||||
if (firstElement) await generateThumbnailForPlaylist(videoPlaylist, firstElement.Video)
|
||||
}
|
||||
|
||||
async function generateThumbnailForPlaylist (videoPlaylist: VideoPlaylistModel, video: VideoModel) {
|
||||
logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url)
|
||||
|
||||
const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename)
|
||||
const thumbnailModel = await createPlaylistMiniatureFromExisting(inputPath, videoPlaylist, true, true)
|
||||
|
||||
thumbnailModel.videoPlaylistId = videoPlaylist.id
|
||||
|
||||
videoPlaylist.Thumbnail = await thumbnailModel.save()
|
||||
}
|
||||
|
|
|
@ -207,7 +207,7 @@ async function processThumbnail (req: express.Request, video: VideoModel) {
|
|||
if (thumbnailField) {
|
||||
const thumbnailPhysicalFile = thumbnailField[ 0 ]
|
||||
|
||||
return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE)
|
||||
return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE, false)
|
||||
}
|
||||
|
||||
return undefined
|
||||
|
@ -218,7 +218,7 @@ async function processPreview (req: express.Request, video: VideoModel) {
|
|||
if (previewField) {
|
||||
const previewPhysicalFile = previewField[0]
|
||||
|
||||
return createVideoMiniatureFromExisting(previewPhysicalFile.path, video, ThumbnailType.PREVIEW)
|
||||
return createVideoMiniatureFromExisting(previewPhysicalFile.path, video, ThumbnailType.PREVIEW, false)
|
||||
}
|
||||
|
||||
return undefined
|
||||
|
|
|
@ -223,13 +223,13 @@ async function addVideo (req: express.Request, res: express.Response) {
|
|||
// Process thumbnail or create it from the video
|
||||
const thumbnailField = req.files['thumbnailfile']
|
||||
const thumbnailModel = thumbnailField
|
||||
? await createVideoMiniatureFromExisting(thumbnailField[0].path, video, ThumbnailType.MINIATURE)
|
||||
? await createVideoMiniatureFromExisting(thumbnailField[0].path, video, ThumbnailType.MINIATURE, false)
|
||||
: await generateVideoMiniature(video, videoFile, ThumbnailType.MINIATURE)
|
||||
|
||||
// Process preview or create it from the video
|
||||
const previewField = req.files['previewfile']
|
||||
const previewModel = previewField
|
||||
? await createVideoMiniatureFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW)
|
||||
? await createVideoMiniatureFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW, false)
|
||||
: await generateVideoMiniature(video, videoFile, ThumbnailType.PREVIEW)
|
||||
|
||||
// Create the torrent file
|
||||
|
@ -329,11 +329,11 @@ async function updateVideo (req: express.Request, res: express.Response) {
|
|||
|
||||
// Process thumbnail or create it from the video
|
||||
const thumbnailModel = req.files && req.files['thumbnailfile']
|
||||
? await createVideoMiniatureFromExisting(req.files['thumbnailfile'][0].path, videoInstance, ThumbnailType.MINIATURE)
|
||||
? await createVideoMiniatureFromExisting(req.files['thumbnailfile'][0].path, videoInstance, ThumbnailType.MINIATURE, false)
|
||||
: undefined
|
||||
|
||||
const previewModel = req.files && req.files['previewfile']
|
||||
? await createVideoMiniatureFromExisting(req.files['previewfile'][0].path, videoInstance, ThumbnailType.PREVIEW)
|
||||
? await createVideoMiniatureFromExisting(req.files['previewfile'][0].path, videoInstance, ThumbnailType.PREVIEW, false)
|
||||
: undefined
|
||||
|
||||
try {
|
||||
|
|
|
@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config'
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LAST_MIGRATION_VERSION = 410
|
||||
const LAST_MIGRATION_VERSION = 415
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
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.BOOLEAN,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
}
|
||||
|
||||
await utils.queryInterface.addColumn('thumbnail', 'automaticallyGenerated', data)
|
||||
}
|
||||
|
||||
{
|
||||
// Set auto generated to true for watch later playlists
|
||||
const query = 'UPDATE thumbnail SET "automaticallyGenerated" = true WHERE "videoPlaylistId" IN ' +
|
||||
'(SELECT id FROM "videoPlaylist" WHERE type = 2)'
|
||||
|
||||
await utils.sequelize.query(query)
|
||||
}
|
||||
}
|
||||
|
||||
function down (options) {
|
||||
throw new Error('Not implemented.')
|
||||
}
|
||||
|
||||
export {
|
||||
up,
|
||||
down
|
||||
}
|
|
@ -105,6 +105,9 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc
|
|||
} catch (err) {
|
||||
logger.warn('Cannot generate thumbnail of %s.', playlistObject.id, { err })
|
||||
}
|
||||
} else if (refreshedPlaylist.hasThumbnail()) {
|
||||
await refreshedPlaylist.Thumbnail.destroy()
|
||||
refreshedPlaylist.Thumbnail = null
|
||||
}
|
||||
|
||||
return resetVideoPlaylistElements(accItems, refreshedPlaylist)
|
||||
|
|
|
@ -12,12 +12,18 @@ import { VideoPlaylistModel } from '../models/video/video-playlist'
|
|||
|
||||
type ImageSize = { height: number, width: number }
|
||||
|
||||
function createPlaylistMiniatureFromExisting (inputPath: string, playlist: VideoPlaylistModel, keepOriginal = false, size?: ImageSize) {
|
||||
function createPlaylistMiniatureFromExisting (
|
||||
inputPath: string,
|
||||
playlist: VideoPlaylistModel,
|
||||
automaticallyGenerated: boolean,
|
||||
keepOriginal = false,
|
||||
size?: ImageSize
|
||||
) {
|
||||
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, existingThumbnail })
|
||||
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail })
|
||||
}
|
||||
|
||||
function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylistModel, size?: ImageSize) {
|
||||
|
@ -35,11 +41,17 @@ function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type:
|
|||
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl })
|
||||
}
|
||||
|
||||
function createVideoMiniatureFromExisting (inputPath: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) {
|
||||
function createVideoMiniatureFromExisting (
|
||||
inputPath: string,
|
||||
video: VideoModel,
|
||||
type: ThumbnailType,
|
||||
automaticallyGenerated: boolean,
|
||||
size?: ImageSize
|
||||
) {
|
||||
const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size)
|
||||
const thumbnailCreator = () => processImage(inputPath, outputPath, { width, height })
|
||||
|
||||
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail })
|
||||
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail })
|
||||
}
|
||||
|
||||
function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) {
|
||||
|
@ -50,7 +62,7 @@ function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, t
|
|||
? () => processImage(ASSETS_PATH.DEFAULT_AUDIO_BACKGROUND, outputPath, { width, height }, true)
|
||||
: () => generateImageFromVideoFile(input, basePath, filename, { height, width })
|
||||
|
||||
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail })
|
||||
return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated: true, existingThumbnail })
|
||||
}
|
||||
|
||||
function createPlaceholderThumbnail (fileUrl: string, video: VideoModel, type: ThumbnailType, size: ImageSize) {
|
||||
|
@ -134,10 +146,11 @@ async function createThumbnailFromFunction (parameters: {
|
|||
height: number,
|
||||
width: number,
|
||||
type: ThumbnailType,
|
||||
automaticallyGenerated?: boolean,
|
||||
fileUrl?: string,
|
||||
existingThumbnail?: ThumbnailModel
|
||||
}) {
|
||||
const { thumbnailCreator, filename, width, height, type, existingThumbnail, fileUrl = null } = parameters
|
||||
const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters
|
||||
|
||||
const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel()
|
||||
|
||||
|
@ -146,6 +159,7 @@ async function createThumbnailFromFunction (parameters: {
|
|||
thumbnail.width = width
|
||||
thumbnail.type = type
|
||||
thumbnail.fileUrl = fileUrl
|
||||
thumbnail.automaticallyGenerated = automaticallyGenerated
|
||||
|
||||
await thumbnailCreator()
|
||||
|
||||
|
|
|
@ -44,6 +44,10 @@ export class ThumbnailModel extends Model<ThumbnailModel> {
|
|||
@Column
|
||||
fileUrl: string
|
||||
|
||||
@AllowNull(true)
|
||||
@Column
|
||||
automaticallyGenerated: boolean
|
||||
|
||||
@ForeignKey(() => VideoModel)
|
||||
@Column
|
||||
videoId: number
|
||||
|
@ -88,7 +92,7 @@ export class ThumbnailModel extends Model<ThumbnailModel> {
|
|||
}
|
||||
|
||||
@AfterDestroy
|
||||
static removeFilesAndSendDelete (instance: ThumbnailModel) {
|
||||
static removeFiles (instance: ThumbnailModel) {
|
||||
logger.info('Removing %s file %s.', ThumbnailModel.types[instance.type].label, instance.filename)
|
||||
|
||||
// Don't block the transaction
|
||||
|
|
|
@ -218,6 +218,24 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel>
|
|||
})
|
||||
}
|
||||
|
||||
static loadFirstElementWithVideoThumbnail (videoPlaylistId: number) {
|
||||
const query = {
|
||||
order: getSort('position'),
|
||||
where: {
|
||||
videoPlaylistId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: VideoModel.scope(VideoScopeNames.WITH_THUMBNAILS),
|
||||
required: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return VideoPlaylistElementModel
|
||||
.findOne(query)
|
||||
}
|
||||
|
||||
static getNextPositionOf (videoPlaylistId: number, transaction?: Transaction) {
|
||||
const query: AggregateOptions<number> = {
|
||||
where: {
|
||||
|
|
|
@ -265,7 +265,6 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
|||
VideoPlaylistElements: VideoPlaylistElementModel[]
|
||||
|
||||
@HasOne(() => ThumbnailModel, {
|
||||
|
||||
foreignKey: {
|
||||
name: 'videoPlaylistId',
|
||||
allowNull: true
|
||||
|
@ -434,6 +433,10 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> {
|
|||
return !!this.Thumbnail
|
||||
}
|
||||
|
||||
hasGeneratedThumbnail () {
|
||||
return this.hasThumbnail() && this.Thumbnail.automaticallyGenerated === true
|
||||
}
|
||||
|
||||
generateThumbnailName () {
|
||||
const extension = '.jpg'
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import './video-hls'
|
|||
import './video-imports'
|
||||
import './video-nsfw'
|
||||
import './video-playlists'
|
||||
import './video-playlist-thumbnails'
|
||||
import './video-privacy'
|
||||
import './video-schedule-update'
|
||||
import './video-transcoder'
|
||||
|
|
262
server/tests/api/videos/video-playlist-thumbnails.ts
Normal file
262
server/tests/api/videos/video-playlist-thumbnails.ts
Normal file
|
@ -0,0 +1,262 @@
|
|||
/* tslint:disable:no-unused-expression */
|
||||
|
||||
import * as chai from 'chai'
|
||||
import 'mocha'
|
||||
import {
|
||||
addVideoInPlaylist,
|
||||
cleanupTests,
|
||||
createVideoPlaylist,
|
||||
doubleFollow,
|
||||
flushAndRunMultipleServers,
|
||||
getVideoPlaylistsList, removeVideoFromPlaylist,
|
||||
ServerInfo,
|
||||
setAccessTokensToServers,
|
||||
setDefaultVideoChannel,
|
||||
testImage,
|
||||
uploadVideoAndGetId,
|
||||
waitJobs,
|
||||
reorderVideosPlaylist
|
||||
} from '../../../../shared/extra-utils'
|
||||
import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
|
||||
|
||||
const expect = chai.expect
|
||||
|
||||
describe('Playlist thumbnail', function () {
|
||||
let servers: ServerInfo[] = []
|
||||
|
||||
let playlistWithoutThumbnail: number
|
||||
let playlistWithThumbnail: number
|
||||
|
||||
let withThumbnailE1: number
|
||||
let withThumbnailE2: number
|
||||
let withoutThumbnailE1: number
|
||||
let withoutThumbnailE2: number
|
||||
|
||||
let video1: number
|
||||
let video2: number
|
||||
|
||||
async function getPlaylistWithoutThumbnail (server: ServerInfo) {
|
||||
const res = await getVideoPlaylistsList(server.url, 0, 10)
|
||||
|
||||
return res.body.data.find(p => p.displayName === 'playlist without thumbnail')
|
||||
}
|
||||
|
||||
async function getPlaylistWithThumbnail (server: ServerInfo) {
|
||||
const res = await getVideoPlaylistsList(server.url, 0, 10)
|
||||
|
||||
return res.body.data.find(p => p.displayName === 'playlist with thumbnail')
|
||||
}
|
||||
|
||||
before(async function () {
|
||||
this.timeout(120000)
|
||||
|
||||
servers = await flushAndRunMultipleServers(2, { transcoding: { enabled: false } })
|
||||
|
||||
// Get the access tokens
|
||||
await setAccessTokensToServers(servers)
|
||||
await setDefaultVideoChannel(servers)
|
||||
|
||||
// Server 1 and server 2 follow each other
|
||||
await doubleFollow(servers[0], servers[1])
|
||||
|
||||
video1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 1' })).id
|
||||
video2 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 2' })).id
|
||||
|
||||
await waitJobs(servers)
|
||||
})
|
||||
|
||||
it('Should automatically update the thumbnail when adding an element', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
const res = await createVideoPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistAttrs: {
|
||||
displayName: 'playlist without thumbnail',
|
||||
privacy: VideoPlaylistPrivacy.PUBLIC,
|
||||
videoChannelId: servers[ 1 ].videoChannel.id
|
||||
}
|
||||
})
|
||||
playlistWithoutThumbnail = res.body.videoPlaylist.id
|
||||
|
||||
const res2 = await addVideoInPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistId: playlistWithoutThumbnail,
|
||||
elementAttrs: { videoId: video1 }
|
||||
})
|
||||
withoutThumbnailE1 = res2.body.videoPlaylistElement.id
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const p = await getPlaylistWithoutThumbnail(server)
|
||||
await testImage(server.url, 'thumbnail-playlist', p.thumbnailPath)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should not update the thumbnail if we explicitly uploaded a thumbnail', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
const res = await createVideoPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistAttrs: {
|
||||
displayName: 'playlist with thumbnail',
|
||||
privacy: VideoPlaylistPrivacy.PUBLIC,
|
||||
videoChannelId: servers[ 1 ].videoChannel.id,
|
||||
thumbnailfile: 'thumbnail.jpg'
|
||||
}
|
||||
})
|
||||
playlistWithThumbnail = res.body.videoPlaylist.id
|
||||
|
||||
const res2 = await addVideoInPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistId: playlistWithThumbnail,
|
||||
elementAttrs: { videoId: video1 }
|
||||
})
|
||||
withThumbnailE1 = res2.body.videoPlaylistElement.id
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const p = await getPlaylistWithThumbnail(server)
|
||||
await testImage(server.url, 'thumbnail', p.thumbnailPath)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should automatically update the thumbnail when moving the first element', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
const res = await addVideoInPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistId: playlistWithoutThumbnail,
|
||||
elementAttrs: { videoId: video2 }
|
||||
})
|
||||
withoutThumbnailE2 = res.body.videoPlaylistElement.id
|
||||
|
||||
await reorderVideosPlaylist({
|
||||
url: servers[1].url,
|
||||
token: servers[1].accessToken,
|
||||
playlistId: playlistWithoutThumbnail,
|
||||
elementAttrs: {
|
||||
startPosition: 1,
|
||||
insertAfterPosition: 2
|
||||
}
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const p = await getPlaylistWithoutThumbnail(server)
|
||||
await testImage(server.url, 'thumbnail-playlist', p.thumbnailPath)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should not update the thumbnail when moving the first element if we explicitly uploaded a thumbnail', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
const res = await addVideoInPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistId: playlistWithThumbnail,
|
||||
elementAttrs: { videoId: video2 }
|
||||
})
|
||||
withThumbnailE2 = res.body.videoPlaylistElement.id
|
||||
|
||||
await reorderVideosPlaylist({
|
||||
url: servers[1].url,
|
||||
token: servers[1].accessToken,
|
||||
playlistId: playlistWithThumbnail,
|
||||
elementAttrs: {
|
||||
startPosition: 1,
|
||||
insertAfterPosition: 2
|
||||
}
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const p = await getPlaylistWithThumbnail(server)
|
||||
await testImage(server.url, 'thumbnail', p.thumbnailPath)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should automatically update the thumbnail when deleting the first element', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await removeVideoFromPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistId: playlistWithoutThumbnail,
|
||||
playlistElementId: withoutThumbnailE1
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const p = await getPlaylistWithoutThumbnail(server)
|
||||
await testImage(server.url, 'thumbnail-playlist', p.thumbnailPath)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should not update the thumbnail when deleting the first element if we explicitly uploaded a thumbnail', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await removeVideoFromPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistId: playlistWithThumbnail,
|
||||
playlistElementId: withThumbnailE1
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const p = await getPlaylistWithThumbnail(server)
|
||||
await testImage(server.url, 'thumbnail', p.thumbnailPath)
|
||||
}
|
||||
})
|
||||
|
||||
it('Should the thumbnail when we delete the last element', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await removeVideoFromPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistId: playlistWithoutThumbnail,
|
||||
playlistElementId: withoutThumbnailE2
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const p = await getPlaylistWithoutThumbnail(server)
|
||||
expect(p.thumbnailPath).to.be.null
|
||||
}
|
||||
})
|
||||
|
||||
it('Should not update the thumbnail when we delete the last element if we explicitly uploaded a thumbnail', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
await removeVideoFromPlaylist({
|
||||
url: servers[ 1 ].url,
|
||||
token: servers[ 1 ].accessToken,
|
||||
playlistId: playlistWithThumbnail,
|
||||
playlistElementId: withThumbnailE2
|
||||
})
|
||||
|
||||
await waitJobs(servers)
|
||||
|
||||
for (const server of servers) {
|
||||
const p = await getPlaylistWithThumbnail(server)
|
||||
await testImage(server.url, 'thumbnail', p.thumbnailPath)
|
||||
}
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await cleanupTests(servers)
|
||||
})
|
||||
})
|
|
@ -344,6 +344,7 @@ describe('Test video playlists', function () {
|
|||
})
|
||||
|
||||
describe('List playlists', function () {
|
||||
|
||||
it('Should correctly list the playlists', async function () {
|
||||
this.timeout(30000)
|
||||
|
||||
|
|
Loading…
Reference in a new issue