diff options
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | scripts/create-generate-storyboard-job.ts | 85 | ||||
-rw-r--r-- | scripts/create-move-video-storage-job.ts | 8 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/generate-storyboard.ts | 5 | ||||
-rw-r--r-- | server/tests/cli/create-generate-storyboard-job.ts | 120 | ||||
-rw-r--r-- | server/tests/cli/index.ts | 1 | ||||
-rw-r--r-- | support/doc/tools.md | 29 |
7 files changed, 247 insertions, 2 deletions
diff --git a/package.json b/package.json index 223156098..e2feea5a2 100644 --- a/package.json +++ b/package.json | |||
@@ -49,6 +49,7 @@ | |||
49 | "regenerate-thumbnails": "node ./dist/scripts/regenerate-thumbnails.js", | 49 | "regenerate-thumbnails": "node ./dist/scripts/regenerate-thumbnails.js", |
50 | "create-import-video-file-job": "node ./dist/scripts/create-import-video-file-job.js", | 50 | "create-import-video-file-job": "node ./dist/scripts/create-import-video-file-job.js", |
51 | "create-move-video-storage-job": "node ./dist/scripts/create-move-video-storage-job.js", | 51 | "create-move-video-storage-job": "node ./dist/scripts/create-move-video-storage-job.js", |
52 | "create-generate-storyboard-job": "node ./dist/scripts/create-generate-storyboard-job.js", | ||
52 | "test": "bash ./scripts/test.sh", | 53 | "test": "bash ./scripts/test.sh", |
53 | "generate-cli-doc": "bash ./scripts/generate-cli-doc.sh", | 54 | "generate-cli-doc": "bash ./scripts/generate-cli-doc.sh", |
54 | "generate-types-package": "ts-node ./packages/types/generate-package.ts", | 55 | "generate-types-package": "ts-node ./packages/types/generate-package.ts", |
diff --git a/scripts/create-generate-storyboard-job.ts b/scripts/create-generate-storyboard-job.ts new file mode 100644 index 000000000..47c08edac --- /dev/null +++ b/scripts/create-generate-storyboard-job.ts | |||
@@ -0,0 +1,85 @@ | |||
1 | import { program } from 'commander' | ||
2 | import { toCompleteUUID } from '@server/helpers/custom-validators/misc' | ||
3 | import { initDatabaseModels } from '@server/initializers/database' | ||
4 | import { JobQueue } from '@server/lib/job-queue' | ||
5 | import { VideoModel } from '@server/models/video/video' | ||
6 | import { StoryboardModel } from '@server/models/video/storyboard' | ||
7 | |||
8 | program | ||
9 | .description('Generate videos storyboard') | ||
10 | .option('-v, --video [videoUUID]', 'Generate the storyboard of a specific video') | ||
11 | .option('-a, --all-videos', 'Generate missing storyboards of local videos') | ||
12 | .parse(process.argv) | ||
13 | |||
14 | const options = program.opts() | ||
15 | |||
16 | if (!options['video'] && !options['allVideos']) { | ||
17 | console.error('You need to choose videos for storyboard generation.') | ||
18 | process.exit(-1) | ||
19 | } | ||
20 | |||
21 | run() | ||
22 | .then(() => process.exit(0)) | ||
23 | .catch(err => { | ||
24 | console.error(err) | ||
25 | process.exit(-1) | ||
26 | }) | ||
27 | |||
28 | async function run () { | ||
29 | await initDatabaseModels(true) | ||
30 | |||
31 | JobQueue.Instance.init() | ||
32 | |||
33 | let ids: number[] = [] | ||
34 | |||
35 | if (options['video']) { | ||
36 | const video = await VideoModel.load(toCompleteUUID(options['video'])) | ||
37 | |||
38 | if (!video) { | ||
39 | console.error('Unknown video ' + options['video']) | ||
40 | process.exit(-1) | ||
41 | } | ||
42 | |||
43 | if (video.remote === true) { | ||
44 | console.error('Cannot process a remote video') | ||
45 | process.exit(-1) | ||
46 | } | ||
47 | |||
48 | if (video.isLive) { | ||
49 | console.error('Cannot process live video') | ||
50 | process.exit(-1) | ||
51 | } | ||
52 | |||
53 | ids.push(video.id) | ||
54 | } else { | ||
55 | ids = await listLocalMissingStoryboards() | ||
56 | } | ||
57 | |||
58 | for (const id of ids) { | ||
59 | const videoFull = await VideoModel.load(id) | ||
60 | |||
61 | if (videoFull.isLive) continue | ||
62 | |||
63 | await JobQueue.Instance.createJob({ | ||
64 | type: 'generate-video-storyboard', | ||
65 | payload: { | ||
66 | videoUUID: videoFull.uuid, | ||
67 | federate: true | ||
68 | } | ||
69 | }) | ||
70 | |||
71 | console.log(`Created generate-storyboard job for ${videoFull.name}.`) | ||
72 | } | ||
73 | } | ||
74 | |||
75 | async function listLocalMissingStoryboards () { | ||
76 | const ids = await VideoModel.listLocalIds() | ||
77 | const results: number[] = [] | ||
78 | |||
79 | for (const id of ids) { | ||
80 | const storyboard = await StoryboardModel.loadByVideo(id) | ||
81 | if (!storyboard) results.push(id) | ||
82 | } | ||
83 | |||
84 | return results | ||
85 | } | ||
diff --git a/scripts/create-move-video-storage-job.ts b/scripts/create-move-video-storage-job.ts index c402115f0..8537114eb 100644 --- a/scripts/create-move-video-storage-job.ts +++ b/scripts/create-move-video-storage-job.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { program } from 'commander' | 1 | import { program } from 'commander' |
2 | import { toCompleteUUID } from '@server/helpers/custom-validators/misc' | ||
2 | import { CONFIG } from '@server/initializers/config' | 3 | import { CONFIG } from '@server/initializers/config' |
3 | import { initDatabaseModels } from '@server/initializers/database' | 4 | import { initDatabaseModels } from '@server/initializers/database' |
4 | import { JobQueue } from '@server/lib/job-queue' | 5 | import { JobQueue } from '@server/lib/job-queue' |
@@ -32,7 +33,10 @@ if (options['toObjectStorage'] && !CONFIG.OBJECT_STORAGE.ENABLED) { | |||
32 | 33 | ||
33 | run() | 34 | run() |
34 | .then(() => process.exit(0)) | 35 | .then(() => process.exit(0)) |
35 | .catch(err => console.error(err)) | 36 | .catch(err => { |
37 | console.error(err) | ||
38 | process.exit(-1) | ||
39 | }) | ||
36 | 40 | ||
37 | async function run () { | 41 | async function run () { |
38 | await initDatabaseModels(true) | 42 | await initDatabaseModels(true) |
@@ -42,7 +46,7 @@ async function run () { | |||
42 | let ids: number[] = [] | 46 | let ids: number[] = [] |
43 | 47 | ||
44 | if (options['video']) { | 48 | if (options['video']) { |
45 | const video = await VideoModel.load(options['video']) | 49 | const video = await VideoModel.load(toCompleteUUID(options['video'])) |
46 | 50 | ||
47 | if (!video) { | 51 | if (!video) { |
48 | console.error('Unknown video ' + options['video']) | 52 | console.error('Unknown video ' + options['video']) |
diff --git a/server/lib/job-queue/handlers/generate-storyboard.ts b/server/lib/job-queue/handlers/generate-storyboard.ts index 652cac272..09b746a3e 100644 --- a/server/lib/job-queue/handlers/generate-storyboard.ts +++ b/server/lib/job-queue/handlers/generate-storyboard.ts | |||
@@ -43,6 +43,11 @@ async function processGenerateStoryboard (job: Job): Promise<void> { | |||
43 | const destination = join(CONFIG.STORAGE.STORYBOARDS_DIR, filename) | 43 | const destination = join(CONFIG.STORAGE.STORYBOARDS_DIR, filename) |
44 | 44 | ||
45 | const totalSprites = buildTotalSprites(video) | 45 | const totalSprites = buildTotalSprites(video) |
46 | if (totalSprites === 0) { | ||
47 | logger.info('Do not generate a storyboard of %s because the video is not long enough', payload.videoUUID, lTags) | ||
48 | return | ||
49 | } | ||
50 | |||
46 | const spriteDuration = Math.round(video.duration / totalSprites) | 51 | const spriteDuration = Math.round(video.duration / totalSprites) |
47 | 52 | ||
48 | const spritesCount = findGridSize({ | 53 | const spritesCount = findGridSize({ |
diff --git a/server/tests/cli/create-generate-storyboard-job.ts b/server/tests/cli/create-generate-storyboard-job.ts new file mode 100644 index 000000000..02a4be8ae --- /dev/null +++ b/server/tests/cli/create-generate-storyboard-job.ts | |||
@@ -0,0 +1,120 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import { expect } from 'chai' | ||
4 | import { readdir, remove } from 'fs-extra' | ||
5 | import { join } from 'path' | ||
6 | import { HttpStatusCode } from '@shared/models' | ||
7 | import { | ||
8 | cleanupTests, | ||
9 | createMultipleServers, | ||
10 | doubleFollow, | ||
11 | makeGetRequest, | ||
12 | PeerTubeServer, | ||
13 | setAccessTokensToServers, | ||
14 | waitJobs | ||
15 | } from '@shared/server-commands' | ||
16 | import { SQLCommand } from '../shared' | ||
17 | |||
18 | function listStoryboardFiles (server: PeerTubeServer) { | ||
19 | const storage = server.getDirectoryPath('storyboards') | ||
20 | |||
21 | return readdir(storage) | ||
22 | } | ||
23 | |||
24 | describe('Test create generate storyboard job', function () { | ||
25 | let servers: PeerTubeServer[] = [] | ||
26 | const uuids: string[] = [] | ||
27 | let sql: SQLCommand | ||
28 | let existingStoryboardName: string | ||
29 | |||
30 | before(async function () { | ||
31 | this.timeout(120000) | ||
32 | |||
33 | // Run server 2 to have transcoding enabled | ||
34 | servers = await createMultipleServers(2) | ||
35 | await setAccessTokensToServers(servers) | ||
36 | |||
37 | await doubleFollow(servers[0], servers[1]) | ||
38 | |||
39 | for (let i = 0; i < 3; i++) { | ||
40 | const { uuid } = await servers[0].videos.quickUpload({ name: 'video ' + i }) | ||
41 | uuids.push(uuid) | ||
42 | } | ||
43 | |||
44 | await waitJobs(servers) | ||
45 | |||
46 | const storage = servers[0].getDirectoryPath('storyboards') | ||
47 | for (const storyboard of await listStoryboardFiles(servers[0])) { | ||
48 | await remove(join(storage, storyboard)) | ||
49 | } | ||
50 | |||
51 | sql = new SQLCommand(servers[0]) | ||
52 | await sql.deleteAll('storyboard') | ||
53 | |||
54 | const { uuid } = await servers[0].videos.quickUpload({ name: 'video 4' }) | ||
55 | uuids.push(uuid) | ||
56 | |||
57 | await waitJobs(servers) | ||
58 | |||
59 | const storyboards = await listStoryboardFiles(servers[0]) | ||
60 | existingStoryboardName = storyboards[0] | ||
61 | }) | ||
62 | |||
63 | it('Should create a storyboard of a video', async function () { | ||
64 | this.timeout(120000) | ||
65 | |||
66 | for (const uuid of [ uuids[0], uuids[3] ]) { | ||
67 | const command = `npm run create-generate-storyboard-job -- -v ${uuid}` | ||
68 | await servers[0].cli.execWithEnv(command) | ||
69 | } | ||
70 | |||
71 | await waitJobs(servers) | ||
72 | |||
73 | { | ||
74 | const storyboards = await listStoryboardFiles(servers[0]) | ||
75 | expect(storyboards).to.have.lengthOf(2) | ||
76 | expect(storyboards).to.not.include(existingStoryboardName) | ||
77 | |||
78 | existingStoryboardName = storyboards[0] | ||
79 | } | ||
80 | |||
81 | for (const server of servers) { | ||
82 | for (const uuid of [ uuids[0], uuids[3] ]) { | ||
83 | const { storyboards } = await server.storyboard.list({ id: uuid }) | ||
84 | expect(storyboards).to.have.lengthOf(1) | ||
85 | |||
86 | await makeGetRequest({ url: server.url, path: storyboards[0].storyboardPath, expectedStatus: HttpStatusCode.OK_200 }) | ||
87 | } | ||
88 | } | ||
89 | }) | ||
90 | |||
91 | it('Should create missing storyboards', async function () { | ||
92 | this.timeout(120000) | ||
93 | |||
94 | const command = `npm run create-generate-storyboard-job -- -a` | ||
95 | await servers[0].cli.execWithEnv(command) | ||
96 | |||
97 | await waitJobs(servers) | ||
98 | |||
99 | { | ||
100 | const storyboards = await listStoryboardFiles(servers[0]) | ||
101 | expect(storyboards).to.have.lengthOf(4) | ||
102 | expect(storyboards).to.include(existingStoryboardName) | ||
103 | } | ||
104 | |||
105 | for (const server of servers) { | ||
106 | for (const uuid of uuids) { | ||
107 | const { storyboards } = await server.storyboard.list({ id: uuid }) | ||
108 | expect(storyboards).to.have.lengthOf(1) | ||
109 | |||
110 | await makeGetRequest({ url: server.url, path: storyboards[0].storyboardPath, expectedStatus: HttpStatusCode.OK_200 }) | ||
111 | } | ||
112 | } | ||
113 | }) | ||
114 | |||
115 | after(async function () { | ||
116 | await sql.cleanup() | ||
117 | |||
118 | await cleanupTests(servers) | ||
119 | }) | ||
120 | }) | ||
diff --git a/server/tests/cli/index.ts b/server/tests/cli/index.ts index 8579be39c..94444ace3 100644 --- a/server/tests/cli/index.ts +++ b/server/tests/cli/index.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | // Order of the tests we want to execute | 1 | // Order of the tests we want to execute |
2 | import './create-import-video-file-job' | 2 | import './create-import-video-file-job' |
3 | import './create-generate-storyboard-job' | ||
3 | import './create-move-video-storage-job' | 4 | import './create-move-video-storage-job' |
4 | import './peertube' | 5 | import './peertube' |
5 | import './plugins' | 6 | import './plugins' |
diff --git a/support/doc/tools.md b/support/doc/tools.md index 39f5ab787..b2fb7cd2c 100644 --- a/support/doc/tools.md +++ b/support/doc/tools.md | |||
@@ -268,6 +268,35 @@ cd /var/www/peertube-docker | |||
268 | docker-compose exec -u peertube peertube npm run create-move-video-storage-job -- --to-object-storage --all-videos | 268 | docker-compose exec -u peertube peertube npm run create-move-video-storage-job -- --to-object-storage --all-videos |
269 | ``` | 269 | ``` |
270 | 270 | ||
271 | <!-- TODO: uncomment when PeerTube 6 is released | ||
272 | ### create-generate-storyboard-job | ||
273 | |||
274 | **PeerTube >= 6.0** | ||
275 | |||
276 | Use this script to generate storyboard of a specific video: | ||
277 | |||
278 | ```bash | ||
279 | # Basic installation | ||
280 | cd /var/www/peertube/peertube-latest | ||
281 | sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run create-generate-storyboard-job -- -v [videoUUID] | ||
282 | |||
283 | # Docker installation | ||
284 | cd /var/www/peertube-docker | ||
285 | docker-compose exec -u peertube peertube npm run create-generate-storyboard-job -- -v [videoUUID] | ||
286 | ``` | ||
287 | |||
288 | The script can also generate all missing storyboards of local videos: | ||
289 | |||
290 | ```bash | ||
291 | # Basic installation | ||
292 | cd /var/www/peertube/peertube-latest | ||
293 | sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run create-generate-storyboard-job -- --all-videos | ||
294 | |||
295 | # Docker installation | ||
296 | cd /var/www/peertube-docker | ||
297 | docker-compose exec -u peertube peertube npm run create-generate-storyboard-job -- --all-videos | ||
298 | ``` | ||
299 | --> | ||
271 | 300 | ||
272 | ### prune-storage.js | 301 | ### prune-storage.js |
273 | 302 | ||