Adapt storyboard sprite ratio

pull/6035/head
Chocobozzz 2023-11-09 08:58:11 +01:00
parent c2cf26eaf7
commit 3da9fbbe39
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
4 changed files with 47 additions and 20 deletions

View File

@ -114,6 +114,12 @@ async function getVideoStreamDimensionsInfo (path: string, existingProbe?: Ffpro
} }
} }
if (videoStream.rotation === '90' || videoStream.rotation === '-90') {
const width = videoStream.width
videoStream.width = videoStream.height
videoStream.height = width
}
return { return {
width: videoStream.width, width: videoStream.width,
height: videoStream.height, height: videoStream.height,

View File

@ -22,10 +22,12 @@ import {
async function checkStoryboard (options: { async function checkStoryboard (options: {
server: PeerTubeServer server: PeerTubeServer
uuid: string uuid: string
spriteHeight?: number
spriteWidth?: number
tilesCount?: number tilesCount?: number
minSize?: number minSize?: number
}) { }) {
const { server, uuid, tilesCount, minSize = 1000 } = options const { server, uuid, tilesCount, spriteHeight = 108, spriteWidth = 192, minSize = 1000 } = options
const { storyboards } = await server.storyboard.list({ id: uuid }) const { storyboards } = await server.storyboard.list({ id: uuid })
@ -34,13 +36,13 @@ async function checkStoryboard (options: {
const storyboard = storyboards[0] const storyboard = storyboards[0]
expect(storyboard.spriteDuration).to.equal(1) expect(storyboard.spriteDuration).to.equal(1)
expect(storyboard.spriteHeight).to.equal(108) expect(storyboard.spriteHeight).to.equal(spriteHeight)
expect(storyboard.spriteWidth).to.equal(192) expect(storyboard.spriteWidth).to.equal(spriteWidth)
expect(storyboard.storyboardPath).to.exist expect(storyboard.storyboardPath).to.exist
if (tilesCount) { if (tilesCount) {
expect(storyboard.totalWidth).to.equal(192 * Math.min(tilesCount, 10)) expect(storyboard.totalWidth).to.equal(spriteWidth * Math.min(tilesCount, 10))
expect(storyboard.totalHeight).to.equal(108 * Math.max((tilesCount / 10), 1)) expect(storyboard.totalHeight).to.equal(spriteHeight * Math.max((tilesCount / 10), 1))
} }
const { body } = await makeGetRequest({ url: server.url, path: storyboard.storyboardPath, expectedStatus: HttpStatusCode.OK_200 }) const { body } = await makeGetRequest({ url: server.url, path: storyboard.storyboardPath, expectedStatus: HttpStatusCode.OK_200 })
@ -83,7 +85,7 @@ describe('Test video storyboard', function () {
await waitJobs(servers) await waitJobs(servers)
for (const server of servers) { for (const server of servers) {
await checkStoryboard({ server, uuid, tilesCount: 100 }) await checkStoryboard({ server, uuid, spriteHeight: 154, tilesCount: 100 })
} }
}) })
@ -134,7 +136,7 @@ describe('Test video storyboard', function () {
await waitJobs(servers) await waitJobs(servers)
for (const server of servers) { for (const server of servers) {
await checkStoryboard({ server, uuid: video.uuid, tilesCount: 3 }) await checkStoryboard({ server, uuid: video.uuid, spriteHeight: 144, tilesCount: 3 })
} }
}) })

View File

@ -864,10 +864,7 @@ const ACTOR_IMAGES_SIZE: { [key in ActorImageType_Type]: { width: number, height
} }
const STORYBOARD = { const STORYBOARD = {
SPRITE_SIZE: { SPRITE_MAX_SIZE: 192,
width: 192,
height: 108
},
SPRITES_MAX_EDGE_COUNT: 10 SPRITES_MAX_EDGE_COUNT: 10
} }

View File

@ -13,7 +13,7 @@ import { VideoPathManager } from '@server/lib/video-path-manager.js'
import { StoryboardModel } from '@server/models/video/storyboard.js' import { StoryboardModel } from '@server/models/video/storyboard.js'
import { VideoModel } from '@server/models/video/video.js' import { VideoModel } from '@server/models/video/video.js'
import { MVideo } from '@server/types/models/index.js' import { MVideo } from '@server/types/models/index.js'
import { FFmpegImage, isAudioFile } from '@peertube/peertube-ffmpeg' import { FFmpegImage, ffprobePromise, getVideoStreamDimensionsInfo, isAudioFile } from '@peertube/peertube-ffmpeg'
import { GenerateStoryboardPayload } from '@peertube/peertube-models' import { GenerateStoryboardPayload } from '@peertube/peertube-models'
import { getImageSizeFromWorker } from '@server/lib/worker/parent-process.js' import { getImageSizeFromWorker } from '@server/lib/worker/parent-process.js'
@ -37,19 +37,32 @@ async function processGenerateStoryboard (job: Job): Promise<void> {
const inputFile = video.getMaxQualityFile() const inputFile = video.getMaxQualityFile()
await VideoPathManager.Instance.makeAvailableVideoFile(inputFile, async videoPath => { await VideoPathManager.Instance.makeAvailableVideoFile(inputFile, async videoPath => {
const isAudio = await isAudioFile(videoPath) const probe = await ffprobePromise(videoPath)
const isAudio = await isAudioFile(videoPath, probe)
if (isAudio) { if (isAudio) {
logger.info('Do not generate a storyboard of %s since the video does not have a video stream', payload.videoUUID, lTags) logger.info('Do not generate a storyboard of %s since the video does not have a video stream', payload.videoUUID, lTags)
return return
} }
const videoStreamInfo = await getVideoStreamDimensionsInfo(videoPath, probe)
let spriteHeight: number
let spriteWidth: number
if (videoStreamInfo.isPortraitMode) {
spriteHeight = STORYBOARD.SPRITE_MAX_SIZE
spriteWidth = Math.round(STORYBOARD.SPRITE_MAX_SIZE / videoStreamInfo.ratio)
} else {
spriteHeight = Math.round(STORYBOARD.SPRITE_MAX_SIZE / videoStreamInfo.ratio)
spriteWidth = STORYBOARD.SPRITE_MAX_SIZE
}
const ffmpeg = new FFmpegImage(getFFmpegCommandWrapperOptions('thumbnail')) const ffmpeg = new FFmpegImage(getFFmpegCommandWrapperOptions('thumbnail'))
const filename = generateImageFilename() const filename = generateImageFilename()
const destination = join(CONFIG.STORAGE.STORYBOARDS_DIR, filename) const destination = join(CONFIG.STORAGE.STORYBOARDS_DIR, filename)
const totalSprites = buildTotalSprites(video) const totalSprites = buildTotalSprites({ video, spriteHeight, spriteWidth })
if (totalSprites === 0) { if (totalSprites === 0) {
logger.info('Do not generate a storyboard of %s because the video is not long enough', payload.videoUUID, lTags) logger.info('Do not generate a storyboard of %s because the video is not long enough', payload.videoUUID, lTags)
return return
@ -64,14 +77,17 @@ async function processGenerateStoryboard (job: Job): Promise<void> {
logger.debug( logger.debug(
'Generating storyboard from video of %s to %s', video.uuid, destination, 'Generating storyboard from video of %s to %s', video.uuid, destination,
{ ...lTags, spritesCount, spriteDuration, videoDuration: video.duration } { ...lTags, spritesCount, spriteDuration, videoDuration: video.duration, spriteHeight, spriteWidth }
) )
await ffmpeg.generateStoryboardFromVideo({ await ffmpeg.generateStoryboardFromVideo({
destination, destination,
path: videoPath, path: videoPath,
sprites: { sprites: {
size: STORYBOARD.SPRITE_SIZE, size: {
height: spriteHeight,
width: spriteWidth
},
count: spritesCount, count: spritesCount,
duration: spriteDuration duration: spriteDuration
} }
@ -95,8 +111,8 @@ async function processGenerateStoryboard (job: Job): Promise<void> {
filename, filename,
totalHeight: imageSize.height, totalHeight: imageSize.height,
totalWidth: imageSize.width, totalWidth: imageSize.width,
spriteHeight: STORYBOARD.SPRITE_SIZE.height, spriteHeight,
spriteWidth: STORYBOARD.SPRITE_SIZE.width, spriteWidth,
spriteDuration, spriteDuration,
videoId: video.id videoId: video.id
}, { transaction }) }, { transaction })
@ -120,8 +136,14 @@ export {
processGenerateStoryboard processGenerateStoryboard
} }
function buildTotalSprites (video: MVideo) { function buildTotalSprites (options: {
const maxSprites = STORYBOARD.SPRITE_SIZE.height * STORYBOARD.SPRITE_SIZE.width video: MVideo
spriteHeight: number
spriteWidth: number
}) {
const { video, spriteHeight, spriteWidth } = options
const maxSprites = spriteHeight * spriteWidth
const totalSprites = Math.min(Math.ceil(video.duration), maxSprites) const totalSprites = Math.min(Math.ceil(video.duration), maxSprites)
// We can generate a single line // We can generate a single line