mirror of https://github.com/Chocobozzz/PeerTube
Adapt storyboard sprite ratio
parent
c2cf26eaf7
commit
3da9fbbe39
|
@ -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,
|
||||||
|
|
|
@ -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 })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue