Add script to generate storyboards
This commit is contained in:
parent
d8f39b126d
commit
9c5cc50133
|
@ -49,6 +49,7 @@
|
||||||
"regenerate-thumbnails": "node ./dist/scripts/regenerate-thumbnails.js",
|
"regenerate-thumbnails": "node ./dist/scripts/regenerate-thumbnails.js",
|
||||||
"create-import-video-file-job": "node ./dist/scripts/create-import-video-file-job.js",
|
"create-import-video-file-job": "node ./dist/scripts/create-import-video-file-job.js",
|
||||||
"create-move-video-storage-job": "node ./dist/scripts/create-move-video-storage-job.js",
|
"create-move-video-storage-job": "node ./dist/scripts/create-move-video-storage-job.js",
|
||||||
|
"create-generate-storyboard-job": "node ./dist/scripts/create-generate-storyboard-job.js",
|
||||||
"test": "bash ./scripts/test.sh",
|
"test": "bash ./scripts/test.sh",
|
||||||
"generate-cli-doc": "bash ./scripts/generate-cli-doc.sh",
|
"generate-cli-doc": "bash ./scripts/generate-cli-doc.sh",
|
||||||
"generate-types-package": "ts-node ./packages/types/generate-package.ts",
|
"generate-types-package": "ts-node ./packages/types/generate-package.ts",
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { program } from 'commander'
|
||||||
|
import { toCompleteUUID } from '@server/helpers/custom-validators/misc'
|
||||||
|
import { initDatabaseModels } from '@server/initializers/database'
|
||||||
|
import { JobQueue } from '@server/lib/job-queue'
|
||||||
|
import { VideoModel } from '@server/models/video/video'
|
||||||
|
import { StoryboardModel } from '@server/models/video/storyboard'
|
||||||
|
|
||||||
|
program
|
||||||
|
.description('Generate videos storyboard')
|
||||||
|
.option('-v, --video [videoUUID]', 'Generate the storyboard of a specific video')
|
||||||
|
.option('-a, --all-videos', 'Generate missing storyboards of local videos')
|
||||||
|
.parse(process.argv)
|
||||||
|
|
||||||
|
const options = program.opts()
|
||||||
|
|
||||||
|
if (!options['video'] && !options['allVideos']) {
|
||||||
|
console.error('You need to choose videos for storyboard generation.')
|
||||||
|
process.exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
run()
|
||||||
|
.then(() => process.exit(0))
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(-1)
|
||||||
|
})
|
||||||
|
|
||||||
|
async function run () {
|
||||||
|
await initDatabaseModels(true)
|
||||||
|
|
||||||
|
JobQueue.Instance.init()
|
||||||
|
|
||||||
|
let ids: number[] = []
|
||||||
|
|
||||||
|
if (options['video']) {
|
||||||
|
const video = await VideoModel.load(toCompleteUUID(options['video']))
|
||||||
|
|
||||||
|
if (!video) {
|
||||||
|
console.error('Unknown video ' + options['video'])
|
||||||
|
process.exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video.remote === true) {
|
||||||
|
console.error('Cannot process a remote video')
|
||||||
|
process.exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video.isLive) {
|
||||||
|
console.error('Cannot process live video')
|
||||||
|
process.exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ids.push(video.id)
|
||||||
|
} else {
|
||||||
|
ids = await listLocalMissingStoryboards()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
const videoFull = await VideoModel.load(id)
|
||||||
|
|
||||||
|
if (videoFull.isLive) continue
|
||||||
|
|
||||||
|
await JobQueue.Instance.createJob({
|
||||||
|
type: 'generate-video-storyboard',
|
||||||
|
payload: {
|
||||||
|
videoUUID: videoFull.uuid,
|
||||||
|
federate: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`Created generate-storyboard job for ${videoFull.name}.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function listLocalMissingStoryboards () {
|
||||||
|
const ids = await VideoModel.listLocalIds()
|
||||||
|
const results: number[] = []
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
const storyboard = await StoryboardModel.loadByVideo(id)
|
||||||
|
if (!storyboard) results.push(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { program } from 'commander'
|
import { program } from 'commander'
|
||||||
|
import { toCompleteUUID } from '@server/helpers/custom-validators/misc'
|
||||||
import { CONFIG } from '@server/initializers/config'
|
import { CONFIG } from '@server/initializers/config'
|
||||||
import { initDatabaseModels } from '@server/initializers/database'
|
import { initDatabaseModels } from '@server/initializers/database'
|
||||||
import { JobQueue } from '@server/lib/job-queue'
|
import { JobQueue } from '@server/lib/job-queue'
|
||||||
|
@ -32,7 +33,10 @@ if (options['toObjectStorage'] && !CONFIG.OBJECT_STORAGE.ENABLED) {
|
||||||
|
|
||||||
run()
|
run()
|
||||||
.then(() => process.exit(0))
|
.then(() => process.exit(0))
|
||||||
.catch(err => console.error(err))
|
.catch(err => {
|
||||||
|
console.error(err)
|
||||||
|
process.exit(-1)
|
||||||
|
})
|
||||||
|
|
||||||
async function run () {
|
async function run () {
|
||||||
await initDatabaseModels(true)
|
await initDatabaseModels(true)
|
||||||
|
@ -42,7 +46,7 @@ async function run () {
|
||||||
let ids: number[] = []
|
let ids: number[] = []
|
||||||
|
|
||||||
if (options['video']) {
|
if (options['video']) {
|
||||||
const video = await VideoModel.load(options['video'])
|
const video = await VideoModel.load(toCompleteUUID(options['video']))
|
||||||
|
|
||||||
if (!video) {
|
if (!video) {
|
||||||
console.error('Unknown video ' + options['video'])
|
console.error('Unknown video ' + options['video'])
|
||||||
|
|
|
@ -43,6 +43,11 @@ async function processGenerateStoryboard (job: Job): Promise<void> {
|
||||||
const destination = join(CONFIG.STORAGE.STORYBOARDS_DIR, filename)
|
const destination = join(CONFIG.STORAGE.STORYBOARDS_DIR, filename)
|
||||||
|
|
||||||
const totalSprites = buildTotalSprites(video)
|
const totalSprites = buildTotalSprites(video)
|
||||||
|
if (totalSprites === 0) {
|
||||||
|
logger.info('Do not generate a storyboard of %s because the video is not long enough', payload.videoUUID, lTags)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const spriteDuration = Math.round(video.duration / totalSprites)
|
const spriteDuration = Math.round(video.duration / totalSprites)
|
||||||
|
|
||||||
const spritesCount = findGridSize({
|
const spritesCount = findGridSize({
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||||
|
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import { readdir, remove } from 'fs-extra'
|
||||||
|
import { join } from 'path'
|
||||||
|
import { HttpStatusCode } from '@shared/models'
|
||||||
|
import {
|
||||||
|
cleanupTests,
|
||||||
|
createMultipleServers,
|
||||||
|
doubleFollow,
|
||||||
|
makeGetRequest,
|
||||||
|
PeerTubeServer,
|
||||||
|
setAccessTokensToServers,
|
||||||
|
waitJobs
|
||||||
|
} from '@shared/server-commands'
|
||||||
|
import { SQLCommand } from '../shared'
|
||||||
|
|
||||||
|
function listStoryboardFiles (server: PeerTubeServer) {
|
||||||
|
const storage = server.getDirectoryPath('storyboards')
|
||||||
|
|
||||||
|
return readdir(storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Test create generate storyboard job', function () {
|
||||||
|
let servers: PeerTubeServer[] = []
|
||||||
|
const uuids: string[] = []
|
||||||
|
let sql: SQLCommand
|
||||||
|
let existingStoryboardName: string
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
this.timeout(120000)
|
||||||
|
|
||||||
|
// Run server 2 to have transcoding enabled
|
||||||
|
servers = await createMultipleServers(2)
|
||||||
|
await setAccessTokensToServers(servers)
|
||||||
|
|
||||||
|
await doubleFollow(servers[0], servers[1])
|
||||||
|
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const { uuid } = await servers[0].videos.quickUpload({ name: 'video ' + i })
|
||||||
|
uuids.push(uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
const storage = servers[0].getDirectoryPath('storyboards')
|
||||||
|
for (const storyboard of await listStoryboardFiles(servers[0])) {
|
||||||
|
await remove(join(storage, storyboard))
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = new SQLCommand(servers[0])
|
||||||
|
await sql.deleteAll('storyboard')
|
||||||
|
|
||||||
|
const { uuid } = await servers[0].videos.quickUpload({ name: 'video 4' })
|
||||||
|
uuids.push(uuid)
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
const storyboards = await listStoryboardFiles(servers[0])
|
||||||
|
existingStoryboardName = storyboards[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should create a storyboard of a video', async function () {
|
||||||
|
this.timeout(120000)
|
||||||
|
|
||||||
|
for (const uuid of [ uuids[0], uuids[3] ]) {
|
||||||
|
const command = `npm run create-generate-storyboard-job -- -v ${uuid}`
|
||||||
|
await servers[0].cli.execWithEnv(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
{
|
||||||
|
const storyboards = await listStoryboardFiles(servers[0])
|
||||||
|
expect(storyboards).to.have.lengthOf(2)
|
||||||
|
expect(storyboards).to.not.include(existingStoryboardName)
|
||||||
|
|
||||||
|
existingStoryboardName = storyboards[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const server of servers) {
|
||||||
|
for (const uuid of [ uuids[0], uuids[3] ]) {
|
||||||
|
const { storyboards } = await server.storyboard.list({ id: uuid })
|
||||||
|
expect(storyboards).to.have.lengthOf(1)
|
||||||
|
|
||||||
|
await makeGetRequest({ url: server.url, path: storyboards[0].storyboardPath, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Should create missing storyboards', async function () {
|
||||||
|
this.timeout(120000)
|
||||||
|
|
||||||
|
const command = `npm run create-generate-storyboard-job -- -a`
|
||||||
|
await servers[0].cli.execWithEnv(command)
|
||||||
|
|
||||||
|
await waitJobs(servers)
|
||||||
|
|
||||||
|
{
|
||||||
|
const storyboards = await listStoryboardFiles(servers[0])
|
||||||
|
expect(storyboards).to.have.lengthOf(4)
|
||||||
|
expect(storyboards).to.include(existingStoryboardName)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const server of servers) {
|
||||||
|
for (const uuid of uuids) {
|
||||||
|
const { storyboards } = await server.storyboard.list({ id: uuid })
|
||||||
|
expect(storyboards).to.have.lengthOf(1)
|
||||||
|
|
||||||
|
await makeGetRequest({ url: server.url, path: storyboards[0].storyboardPath, expectedStatus: HttpStatusCode.OK_200 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
await sql.cleanup()
|
||||||
|
|
||||||
|
await cleanupTests(servers)
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,5 +1,6 @@
|
||||||
// Order of the tests we want to execute
|
// Order of the tests we want to execute
|
||||||
import './create-import-video-file-job'
|
import './create-import-video-file-job'
|
||||||
|
import './create-generate-storyboard-job'
|
||||||
import './create-move-video-storage-job'
|
import './create-move-video-storage-job'
|
||||||
import './peertube'
|
import './peertube'
|
||||||
import './plugins'
|
import './plugins'
|
||||||
|
|
|
@ -268,6 +268,35 @@ cd /var/www/peertube-docker
|
||||||
docker-compose exec -u peertube peertube npm run create-move-video-storage-job -- --to-object-storage --all-videos
|
docker-compose exec -u peertube peertube npm run create-move-video-storage-job -- --to-object-storage --all-videos
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<!-- TODO: uncomment when PeerTube 6 is released
|
||||||
|
### create-generate-storyboard-job
|
||||||
|
|
||||||
|
**PeerTube >= 6.0**
|
||||||
|
|
||||||
|
Use this script to generate storyboard of a specific video:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic installation
|
||||||
|
cd /var/www/peertube/peertube-latest
|
||||||
|
sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run create-generate-storyboard-job -- -v [videoUUID]
|
||||||
|
|
||||||
|
# Docker installation
|
||||||
|
cd /var/www/peertube-docker
|
||||||
|
docker-compose exec -u peertube peertube npm run create-generate-storyboard-job -- -v [videoUUID]
|
||||||
|
```
|
||||||
|
|
||||||
|
The script can also generate all missing storyboards of local videos:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic installation
|
||||||
|
cd /var/www/peertube/peertube-latest
|
||||||
|
sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run create-generate-storyboard-job -- --all-videos
|
||||||
|
|
||||||
|
# Docker installation
|
||||||
|
cd /var/www/peertube-docker
|
||||||
|
docker-compose exec -u peertube peertube npm run create-generate-storyboard-job -- --all-videos
|
||||||
|
```
|
||||||
|
-->
|
||||||
|
|
||||||
### prune-storage.js
|
### prune-storage.js
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue