diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 407907e53..baddba758 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,6 +46,7 @@ jobs: PGHOST: localhost NODE_PENDING_JOB_WAIT: 250 ENABLE_OBJECT_STORAGE_TESTS: true + ENABLE_FFMPEG_THUMBNAIL_PIXEL_COMPARISON_TESTS: true OBJECT_STORAGE_SCALEWAY_KEY_ID: ${{ secrets.OBJECT_STORAGE_SCALEWAY_KEY_ID }} OBJECT_STORAGE_SCALEWAY_ACCESS_KEY: ${{ secrets.OBJECT_STORAGE_SCALEWAY_ACCESS_KEY }} YOUTUBE_DL_DOWNLOAD_BEARER_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/server/controllers/api/videos/upload.ts b/server/controllers/api/videos/upload.ts index 86ab4591e..6c471ff90 100644 --- a/server/controllers/api/videos/upload.ts +++ b/server/controllers/api/videos/upload.ts @@ -111,7 +111,7 @@ async function addVideoLegacy (req: express.Request, res: express.Response) { async function addVideoResumable (req: express.Request, res: express.Response) { const videoPhysicalFile = res.locals.videoFileResumable const videoInfo = videoPhysicalFile.metadata - const files = { previewfile: videoInfo.previewfile } + const files = { previewfile: videoInfo.previewfile, thumbnailfile: videoInfo.thumbnailfile } const response = await addVideo({ req, res, videoPhysicalFile, videoInfo, files }) await Redis.Instance.setUploadSession(req.query.upload_id, response) diff --git a/server/lib/object-storage/pre-signed-urls.ts b/server/lib/object-storage/pre-signed-urls.ts new file mode 100644 index 000000000..46a0750a1 --- /dev/null +++ b/server/lib/object-storage/pre-signed-urls.ts @@ -0,0 +1,41 @@ +import { GetObjectCommand } from '@aws-sdk/client-s3' +import { getSignedUrl } from '@aws-sdk/s3-request-presigner' +import { CONFIG } from '@server/initializers/config' +import { MStreamingPlaylistVideo, MVideoFile } from '@server/types/models' +import { generateHLSObjectStorageKey, generateWebTorrentObjectStorageKey } from './keys' +import { buildKey, getClient } from './shared' + +export function generateWebVideoPresignedUrl (options: { + file: MVideoFile + downloadFilename: string +}) { + const { file, downloadFilename } = options + + const key = generateWebTorrentObjectStorageKey(file.filename) + + const command = new GetObjectCommand({ + Bucket: CONFIG.OBJECT_STORAGE.VIDEOS.BUCKET_NAME, + Key: buildKey(key, CONFIG.OBJECT_STORAGE.VIDEOS), + ResponseContentDisposition: `attachment; filename=${downloadFilename}` + }) + + return getSignedUrl(getClient(), command, { expiresIn: 3600 * 24 }) +} + +export function generateHLSFilePresignedUrl (options: { + streamingPlaylist: MStreamingPlaylistVideo + file: MVideoFile + downloadFilename: string +}) { + const { streamingPlaylist, file, downloadFilename } = options + + const key = generateHLSObjectStorageKey(streamingPlaylist, file.filename) + + const command = new GetObjectCommand({ + Bucket: CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS.BUCKET_NAME, + Key: buildKey(key, CONFIG.OBJECT_STORAGE.STREAMING_PLAYLISTS), + ResponseContentDisposition: `attachment; filename=${downloadFilename}` + }) + + return getSignedUrl(getClient(), command, { expiresIn: 3600 * 24 }) +} diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index fc2f95aa5..9034772c0 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts @@ -223,10 +223,12 @@ const videosAddResumableInitValidator = getCommonVideoEditAttributes().concat([ if (!isValidPasswordProtectedPrivacy(req, res)) return cleanup() - // multer required unsetting the Content-Type, now we can set it for node-uploadx + // Multer required unsetting the Content-Type, now we can set it for node-uploadx req.headers['content-type'] = 'application/json; charset=utf-8' - // place previewfile in metadata so that uploadx saves it in .META + + // Place thumbnail/previewfile in metadata so that uploadx saves it in .META if (req.files?.['previewfile']) req.body.previewfile = req.files['previewfile'] + if (req.files?.['thumbnailfile']) req.body.thumbnailfile = req.files['thumbnailfile'] return next() } diff --git a/server/tests/api/check-params/live.ts b/server/tests/api/check-params/live.ts index 406a96824..5021db516 100644 --- a/server/tests/api/check-params/live.ts +++ b/server/tests/api/check-params/live.ts @@ -194,7 +194,7 @@ describe('Test video lives API validator', function () { it('Should fail with a big thumbnail file', async function () { const fields = baseCorrectParams const attaches = { - thumbnailfile: buildAbsoluteFixturePath('preview-big.png') + thumbnailfile: buildAbsoluteFixturePath('custom-preview-big.png') } await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) @@ -212,7 +212,7 @@ describe('Test video lives API validator', function () { it('Should fail with a big preview file', async function () { const fields = baseCorrectParams const attaches = { - previewfile: buildAbsoluteFixturePath('preview-big.png') + previewfile: buildAbsoluteFixturePath('custom-preview-big.png') } await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) diff --git a/server/tests/api/check-params/runners.ts b/server/tests/api/check-params/runners.ts index 48821b678..4ba90802f 100644 --- a/server/tests/api/check-params/runners.ts +++ b/server/tests/api/check-params/runners.ts @@ -752,7 +752,7 @@ describe('Test managing runners', function () { }) it('Should fail with an invalid vod audio merge payload', async function () { - const attributes = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' } + const attributes = { name: 'audio_with_preview', previewfile: 'custom-preview.jpg', fixture: 'sample.ogg' } await server.videos.upload({ attributes, mode: 'legacy' }) await waitJobs([ server ]) diff --git a/server/tests/api/check-params/video-imports.ts b/server/tests/api/check-params/video-imports.ts index 7f19b9ee9..8c6f43c12 100644 --- a/server/tests/api/check-params/video-imports.ts +++ b/server/tests/api/check-params/video-imports.ts @@ -244,7 +244,7 @@ describe('Test video imports API validator', function () { it('Should fail with a big thumbnail file', async function () { const fields = baseCorrectParams const attaches = { - thumbnailfile: buildAbsoluteFixturePath('preview-big.png') + thumbnailfile: buildAbsoluteFixturePath('custom-preview-big.png') } await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) @@ -262,7 +262,7 @@ describe('Test video imports API validator', function () { it('Should fail with a big preview file', async function () { const fields = baseCorrectParams const attaches = { - previewfile: buildAbsoluteFixturePath('preview-big.png') + previewfile: buildAbsoluteFixturePath('custom-preview-big.png') } await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) diff --git a/server/tests/api/check-params/video-playlists.ts b/server/tests/api/check-params/video-playlists.ts index 8090897c1..8c3233e0b 100644 --- a/server/tests/api/check-params/video-playlists.ts +++ b/server/tests/api/check-params/video-playlists.ts @@ -196,7 +196,7 @@ describe('Test video playlists API validator', function () { attributes: { displayName: 'display name', privacy: VideoPlaylistPrivacy.UNLISTED, - thumbnailfile: 'thumbnail.jpg', + thumbnailfile: 'custom-thumbnail.jpg', videoChannelId: server.store.channel.id, ...attributes @@ -260,7 +260,7 @@ describe('Test video playlists API validator', function () { }) it('Should fail with a thumbnail file too big', async function () { - const params = getBase({ thumbnailfile: 'preview-big.png' }) + const params = getBase({ thumbnailfile: 'custom-preview-big.png' }) await command.create(params) await command.update(getUpdate(params, playlist.shortUUID)) diff --git a/server/tests/api/check-params/video-studio.ts b/server/tests/api/check-params/video-studio.ts index add8d9164..4ac0d93ed 100644 --- a/server/tests/api/check-params/video-studio.ts +++ b/server/tests/api/check-params/video-studio.ts @@ -293,7 +293,7 @@ describe('Test video studio API validator', function () { it('Should succeed with the correct params', async function () { this.timeout(120000) - await addWatermark('thumbnail.jpg', HttpStatusCode.NO_CONTENT_204) + await addWatermark('custom-thumbnail.jpg', HttpStatusCode.NO_CONTENT_204) await waitJobs([ server ]) }) @@ -322,8 +322,8 @@ describe('Test video studio API validator', function () { }) it('Should fail with an invalid file', async function () { - await addIntroOutro('add-intro', 'thumbnail.jpg') - await addIntroOutro('add-outro', 'thumbnail.jpg') + await addIntroOutro('add-intro', 'custom-thumbnail.jpg') + await addIntroOutro('add-outro', 'custom-thumbnail.jpg') }) it('Should fail with a file that does not contain video stream', async function () { diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index 094ab6891..6ee1955a7 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts @@ -384,7 +384,7 @@ describe('Test videos API validator', function () { it('Should fail with a big thumbnail file', async function () { const fields = baseCorrectParams const attaches = { - thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'preview-big.png'), + thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'custom-preview-big.png'), fixture: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') } @@ -404,7 +404,7 @@ describe('Test videos API validator', function () { it('Should fail with a big preview file', async function () { const fields = baseCorrectParams const attaches = { - previewfile: join(root(), 'server', 'tests', 'fixtures', 'preview-big.png'), + previewfile: join(root(), 'server', 'tests', 'fixtures', 'custom-preview-big.png'), fixture: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') } @@ -615,7 +615,7 @@ describe('Test videos API validator', function () { it('Should fail with a big thumbnail file', async function () { const fields = baseCorrectParams const attaches = { - thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'preview-big.png') + thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'custom-preview-big.png') } await makeUploadRequest({ @@ -647,7 +647,7 @@ describe('Test videos API validator', function () { it('Should fail with a big preview file', async function () { const fields = baseCorrectParams const attaches = { - previewfile: join(root(), 'server', 'tests', 'fixtures', 'preview-big.png') + previewfile: join(root(), 'server', 'tests', 'fixtures', 'custom-preview-big.png') } await makeUploadRequest({ diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts index 7ab67b126..2b302a8a2 100644 --- a/server/tests/api/live/live.ts +++ b/server/tests/api/live/live.ts @@ -2,7 +2,7 @@ import { expect } from 'chai' import { basename, join } from 'path' -import { SQLCommand, testImage, testLiveVideoResolutions } from '@server/tests/shared' +import { SQLCommand, testImageGeneratedByFFmpeg, testLiveVideoResolutions } from '@server/tests/shared' import { getAllFiles, wait } from '@shared/core-utils' import { ffprobePromise, getVideoStream } from '@shared/ffmpeg' import { @@ -121,8 +121,8 @@ describe('Test live', function () { expect(video.downloadEnabled).to.be.false expect(video.privacy.id).to.equal(VideoPrivacy.PUBLIC) - await testImage(server.url, 'video_short1-preview.webm', video.previewPath) - await testImage(server.url, 'video_short1.webm', video.thumbnailPath) + await testImageGeneratedByFFmpeg(server.url, 'video_short1-preview.webm', video.previewPath) + await testImageGeneratedByFFmpeg(server.url, 'video_short1.webm', video.thumbnailPath) const live = await server.live.get({ videoId: liveVideoUUID }) diff --git a/server/tests/api/runners/runner-studio-transcoding.ts b/server/tests/api/runners/runner-studio-transcoding.ts index 41c556775..443a9d02a 100644 --- a/server/tests/api/runners/runner-studio-transcoding.ts +++ b/server/tests/api/runners/runner-studio-transcoding.ts @@ -104,7 +104,7 @@ describe('Test runner video studio transcoding', function () { { name: 'add-watermark' as 'add-watermark', options: { - file: 'thumbnail.png' + file: 'custom-thumbnail.png' } }, { diff --git a/server/tests/api/runners/runner-vod-transcoding.ts b/server/tests/api/runners/runner-vod-transcoding.ts index d9da0f40d..ca16d9c10 100644 --- a/server/tests/api/runners/runner-vod-transcoding.ts +++ b/server/tests/api/runners/runner-vod-transcoding.ts @@ -424,7 +424,7 @@ describe('Test runner VOD transcoding', function () { await servers[0].config.enableTranscoding(true, true) - const attributes = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' } + const attributes = { name: 'audio_with_preview', previewfile: 'custom-preview.jpg', fixture: 'sample.ogg' } const { uuid } = await servers[0].videos.upload({ attributes, mode: 'legacy' }) videoUUID = uuid diff --git a/server/tests/api/transcoding/transcoder.ts b/server/tests/api/transcoding/transcoder.ts index 8a0a7f6d2..3cd247a24 100644 --- a/server/tests/api/transcoding/transcoder.ts +++ b/server/tests/api/transcoding/transcoder.ts @@ -353,7 +353,7 @@ describe('Test video transcoding', function () { it('Should merge an audio file with the preview file', async function () { this.timeout(60_000) - const attributes = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' } + const attributes = { name: 'audio_with_preview', previewfile: 'custom-preview.jpg', fixture: 'sample.ogg' } await servers[1].videos.upload({ attributes, mode }) await waitJobs(servers) @@ -416,7 +416,7 @@ describe('Test video transcoding', function () { } }) - const attributes = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' } + const attributes = { name: 'audio_with_preview', previewfile: 'custom-preview.jpg', fixture: 'sample.ogg' } const { id } = await servers[1].videos.upload({ attributes, mode }) await waitJobs(servers) diff --git a/server/tests/api/transcoding/video-studio.ts b/server/tests/api/transcoding/video-studio.ts index d1298caf7..e97f2b689 100644 --- a/server/tests/api/transcoding/video-studio.ts +++ b/server/tests/api/transcoding/video-studio.ts @@ -241,7 +241,7 @@ describe('Test video studio', function () { { name: 'add-watermark', options: { - file: 'thumbnail.png' + file: 'custom-thumbnail.png' } } ]) diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts index 27ba00d3d..e9aa0e3a1 100644 --- a/server/tests/api/videos/multiple-servers.ts +++ b/server/tests/api/videos/multiple-servers.ts @@ -9,7 +9,7 @@ import { completeVideoCheck, dateIsValid, saveVideoInServers, - testImage + testImageGeneratedByFFmpeg } from '@server/tests/shared' import { buildAbsoluteFixturePath, wait } from '@shared/core-utils' import { HttpStatusCode, VideoCommentThreadTree, VideoPrivacy } from '@shared/models' @@ -70,8 +70,9 @@ describe('Test multiple servers', function () { }) describe('Should upload the video and propagate on each server', function () { + it('Should upload the video on server 1 and propagate on each server', async function () { - this.timeout(25000) + this.timeout(60000) const attributes = { name: 'my super name for server 1', @@ -175,8 +176,8 @@ describe('Test multiple servers', function () { support: 'my super support text for server 2', tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ], fixture: 'video_short2.webm', - thumbnailfile: 'thumbnail.jpg', - previewfile: 'preview.jpg' + thumbnailfile: 'custom-thumbnail.jpg', + previewfile: 'custom-preview.jpg' } await servers[1].videos.upload({ token: userAccessToken, attributes, mode: 'resumable' }) @@ -229,8 +230,8 @@ describe('Test multiple servers', function () { size: 750000 } ], - thumbnailfile: 'thumbnail', - previewfile: 'preview' + thumbnailfile: 'custom-thumbnail', + previewfile: 'custom-preview' } const { data } = await server.videos.list() @@ -619,9 +620,9 @@ describe('Test multiple servers', function () { description: 'my super description updated', support: 'my super support text updated', tags: [ 'tag_up_1', 'tag_up_2' ], - thumbnailfile: 'thumbnail.jpg', + thumbnailfile: 'custom-thumbnail.jpg', originallyPublishedAt: '2019-02-11T13:38:14.449Z', - previewfile: 'preview.jpg' + previewfile: 'custom-preview.jpg' } updatedAtMin = new Date() @@ -674,8 +675,8 @@ describe('Test multiple servers', function () { size: 292677 } ], - thumbnailfile: 'thumbnail', - previewfile: 'preview' + thumbnailfile: 'custom-thumbnail', + previewfile: 'custom-preview' } await completeVideoCheck({ server, originServer: servers[2], videoUUID: videoUpdated.uuid, attributes: checkAttributes }) } @@ -685,7 +686,7 @@ describe('Test multiple servers', function () { this.timeout(30000) const attributes = { - thumbnailfile: 'thumbnail.jpg' + thumbnailfile: 'custom-thumbnail.jpg' } updatedAtMin = new Date() @@ -761,7 +762,7 @@ describe('Test multiple servers', function () { for (const server of servers) { const video = await server.videos.get({ id: videoUUID }) - await testImage(server.url, 'video_short1-preview.webm', video.previewPath) + await testImageGeneratedByFFmpeg(server.url, 'video_short1-preview.webm', video.previewPath) } }) }) diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts index 0cb64d5a5..66414aa5b 100644 --- a/server/tests/api/videos/single-server.ts +++ b/server/tests/api/videos/single-server.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import { expect } from 'chai' -import { checkVideoFilesWereRemoved, completeVideoCheck, testImage } from '@server/tests/shared' +import { checkVideoFilesWereRemoved, completeVideoCheck, testImageGeneratedByFFmpeg } from '@server/tests/shared' import { wait } from '@shared/core-utils' import { Video, VideoPrivacy } from '@shared/models' import { @@ -260,7 +260,7 @@ describe('Test a single server', function () { for (const video of data) { const videoName = video.name.replace(' name', '') - await testImage(server.url, videoName, video.thumbnailPath) + await testImageGeneratedByFFmpeg(server.url, videoName, video.thumbnailPath) } }) diff --git a/server/tests/api/videos/video-imports.ts b/server/tests/api/videos/video-imports.ts index 192b2aeb9..a684a55a0 100644 --- a/server/tests/api/videos/video-imports.ts +++ b/server/tests/api/videos/video-imports.ts @@ -3,7 +3,7 @@ import { expect } from 'chai' import { pathExists, readdir, remove } from 'fs-extra' import { join } from 'path' -import { FIXTURE_URLS, testCaptionFile, testImage } from '@server/tests/shared' +import { FIXTURE_URLS, testCaptionFile, testImageGeneratedByFFmpeg } from '@server/tests/shared' import { areHttpImportTestsDisabled } from '@shared/core-utils' import { CustomConfig, HttpStatusCode, Video, VideoImportState, VideoPrivacy, VideoResolution, VideoState } from '@shared/models' import { @@ -67,7 +67,7 @@ async function checkVideoServer2 (server: PeerTubeServer, id: number | string) { expect(video.description).to.equal('my super description') expect(video.tags).to.deep.equal([ 'supertag1', 'supertag2' ]) - await testImage(server.url, 'thumbnail', video.thumbnailPath) + await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', video.thumbnailPath) expect(video.files).to.have.lengthOf(1) @@ -126,8 +126,8 @@ describe('Test video imports', function () { ? '_yt_dlp' : '' - await testImage(servers[0].url, 'video_import_thumbnail' + suffix, video.thumbnailPath) - await testImage(servers[0].url, 'video_import_preview' + suffix, video.previewPath) + await testImageGeneratedByFFmpeg(servers[0].url, 'video_import_thumbnail' + suffix, video.thumbnailPath) + await testImageGeneratedByFFmpeg(servers[0].url, 'video_import_preview' + suffix, video.previewPath) } const bodyCaptions = await servers[0].captions.list({ videoId: video.id }) @@ -266,7 +266,7 @@ describe('Test video imports', function () { name: 'my super name', description: 'my super description', tags: [ 'supertag1', 'supertag2' ], - thumbnailfile: 'thumbnail.jpg' + thumbnailfile: 'custom-thumbnail.jpg' } }) expect(video.name).to.equal('my super name') diff --git a/server/tests/api/videos/video-playlist-thumbnails.ts b/server/tests/api/videos/video-playlist-thumbnails.ts index 356939b93..c274c20bf 100644 --- a/server/tests/api/videos/video-playlist-thumbnails.ts +++ b/server/tests/api/videos/video-playlist-thumbnails.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import { expect } from 'chai' -import { testImage } from '@server/tests/shared' +import { testImageGeneratedByFFmpeg } from '@server/tests/shared' import { VideoPlaylistPrivacy } from '@shared/models' import { cleanupTests, @@ -83,7 +83,7 @@ describe('Playlist thumbnail', function () { for (const server of servers) { const p = await getPlaylistWithoutThumbnail(server) - await testImage(server.url, 'thumbnail-playlist', p.thumbnailPath) + await testImageGeneratedByFFmpeg(server.url, 'thumbnail-playlist', p.thumbnailPath) } }) @@ -95,7 +95,7 @@ describe('Playlist thumbnail', function () { displayName: 'playlist with thumbnail', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[1].store.channel.id, - thumbnailfile: 'thumbnail.jpg' + thumbnailfile: 'custom-thumbnail.jpg' } }) playlistWithThumbnailId = created.id @@ -110,7 +110,7 @@ describe('Playlist thumbnail', function () { for (const server of servers) { const p = await getPlaylistWithThumbnail(server) - await testImage(server.url, 'thumbnail', p.thumbnailPath) + await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', p.thumbnailPath) } }) @@ -135,7 +135,7 @@ describe('Playlist thumbnail', function () { for (const server of servers) { const p = await getPlaylistWithoutThumbnail(server) - await testImage(server.url, 'thumbnail-playlist', p.thumbnailPath) + await testImageGeneratedByFFmpeg(server.url, 'thumbnail-playlist', p.thumbnailPath) } }) @@ -160,7 +160,7 @@ describe('Playlist thumbnail', function () { for (const server of servers) { const p = await getPlaylistWithThumbnail(server) - await testImage(server.url, 'thumbnail', p.thumbnailPath) + await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', p.thumbnailPath) } }) @@ -176,7 +176,7 @@ describe('Playlist thumbnail', function () { for (const server of servers) { const p = await getPlaylistWithoutThumbnail(server) - await testImage(server.url, 'thumbnail-playlist', p.thumbnailPath) + await testImageGeneratedByFFmpeg(server.url, 'thumbnail-playlist', p.thumbnailPath) } }) @@ -192,7 +192,7 @@ describe('Playlist thumbnail', function () { for (const server of servers) { const p = await getPlaylistWithThumbnail(server) - await testImage(server.url, 'thumbnail', p.thumbnailPath) + await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', p.thumbnailPath) } }) @@ -224,7 +224,7 @@ describe('Playlist thumbnail', function () { for (const server of servers) { const p = await getPlaylistWithThumbnail(server) - await testImage(server.url, 'thumbnail', p.thumbnailPath) + await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', p.thumbnailPath) } }) diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts index 9277b49f4..3bfa874cb 100644 --- a/server/tests/api/videos/video-playlists.ts +++ b/server/tests/api/videos/video-playlists.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ import { expect } from 'chai' -import { checkPlaylistFilesWereRemoved, testImage } from '@server/tests/shared' +import { checkPlaylistFilesWereRemoved, testImageGeneratedByFFmpeg } from '@server/tests/shared' import { wait } from '@shared/core-utils' import { uuidToShort } from '@shared/extra-utils' import { @@ -133,7 +133,7 @@ describe('Test video playlists', function () { displayName: 'my super playlist', privacy: VideoPlaylistPrivacy.PUBLIC, description: 'my super description', - thumbnailfile: 'thumbnail.jpg', + thumbnailfile: 'custom-thumbnail.jpg', videoChannelId: servers[0].store.channel.id } }) @@ -225,7 +225,7 @@ describe('Test video playlists', function () { displayName: 'my super playlist', privacy: VideoPlaylistPrivacy.PUBLIC, description: 'my super description', - thumbnailfile: 'thumbnail.jpg', + thumbnailfile: 'custom-thumbnail.jpg', videoChannelId: servers[0].store.channel.id } }) @@ -286,7 +286,7 @@ describe('Test video playlists', function () { attributes: { displayName: 'playlist 3', privacy: VideoPlaylistPrivacy.PUBLIC, - thumbnailfile: 'thumbnail.jpg', + thumbnailfile: 'custom-thumbnail.jpg', videoChannelId: servers[1].store.channel.id } }) @@ -314,11 +314,11 @@ describe('Test video playlists', function () { const playlist2 = body.data.find(p => p.displayName === 'playlist 2') expect(playlist2).to.not.be.undefined - await testImage(server.url, 'thumbnail-playlist', playlist2.thumbnailPath) + await testImageGeneratedByFFmpeg(server.url, 'thumbnail-playlist', playlist2.thumbnailPath) const playlist3 = body.data.find(p => p.displayName === 'playlist 3') expect(playlist3).to.not.be.undefined - await testImage(server.url, 'thumbnail', playlist3.thumbnailPath) + await testImageGeneratedByFFmpeg(server.url, 'custom-thumbnail', playlist3.thumbnailPath) } const body = await servers[2].playlists.list({ start: 0, count: 5 }) @@ -336,7 +336,7 @@ describe('Test video playlists', function () { const playlist2 = body.data.find(p => p.displayName === 'playlist 2') expect(playlist2).to.not.be.undefined - await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath) + await testImageGeneratedByFFmpeg(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath) expect(body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined }) @@ -502,7 +502,7 @@ describe('Test video playlists', function () { displayName: 'playlist 3 updated', description: 'description updated', privacy: VideoPlaylistPrivacy.UNLISTED, - thumbnailfile: 'thumbnail.jpg', + thumbnailfile: 'custom-thumbnail.jpg', videoChannelId: servers[1].store.channel.id }, playlistId: playlistServer2Id2 diff --git a/server/tests/api/videos/video-storyboard.ts b/server/tests/api/videos/video-storyboard.ts index 5fde6ce45..fc4b4450f 100644 --- a/server/tests/api/videos/video-storyboard.ts +++ b/server/tests/api/videos/video-storyboard.ts @@ -110,7 +110,11 @@ describe('Test video storyboard', function () { await waitJobs(servers) for (const server of servers) { - await checkStoryboard({ server, uuid, tilesCount: 6, minSize: 250 }) + try { + await checkStoryboard({ server, uuid, tilesCount: 6, minSize: 250 }) + } catch { // FIXME: to remove after ffmpeg CI upgrade, ffmpeg CI version (4.3) generates a 7.6s length video + await checkStoryboard({ server, uuid, tilesCount: 8, minSize: 250 }) + } } }) diff --git a/server/tests/cli/prune-storage.ts b/server/tests/cli/prune-storage.ts index 8bdf2136d..dc8ecd3d3 100644 --- a/server/tests/cli/prune-storage.ts +++ b/server/tests/cli/prune-storage.ts @@ -85,7 +85,7 @@ describe('Test prune storage scripts', function () { displayName: 'playlist', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: server.store.channel.id, - thumbnailfile: 'thumbnail.jpg' + thumbnailfile: 'custom-thumbnail.jpg' } }) } diff --git a/server/tests/fixtures/custom-preview-big.png b/server/tests/fixtures/custom-preview-big.png new file mode 100644 index 000000000..03d171af3 Binary files /dev/null and b/server/tests/fixtures/custom-preview-big.png differ diff --git a/server/tests/fixtures/custom-preview.jpg b/server/tests/fixtures/custom-preview.jpg new file mode 100644 index 000000000..5a039d830 Binary files /dev/null and b/server/tests/fixtures/custom-preview.jpg differ diff --git a/server/tests/fixtures/custom-thumbnail-big.jpg b/server/tests/fixtures/custom-thumbnail-big.jpg new file mode 100644 index 000000000..08375e425 Binary files /dev/null and b/server/tests/fixtures/custom-thumbnail-big.jpg differ diff --git a/server/tests/fixtures/custom-thumbnail.jpg b/server/tests/fixtures/custom-thumbnail.jpg new file mode 100644 index 000000000..ef818442d Binary files /dev/null and b/server/tests/fixtures/custom-thumbnail.jpg differ diff --git a/server/tests/fixtures/custom-thumbnail.png b/server/tests/fixtures/custom-thumbnail.png new file mode 100644 index 000000000..9f34daec1 Binary files /dev/null and b/server/tests/fixtures/custom-thumbnail.png differ diff --git a/server/tests/fixtures/preview-big.png b/server/tests/fixtures/preview-big.png deleted file mode 100644 index 612e297f1..000000000 Binary files a/server/tests/fixtures/preview-big.png and /dev/null differ diff --git a/server/tests/fixtures/preview.jpg b/server/tests/fixtures/preview.jpg deleted file mode 100644 index 1421da738..000000000 Binary files a/server/tests/fixtures/preview.jpg and /dev/null differ diff --git a/server/tests/fixtures/thumbnail-big.jpg b/server/tests/fixtures/thumbnail-big.jpg deleted file mode 100644 index 537720d24..000000000 Binary files a/server/tests/fixtures/thumbnail-big.jpg and /dev/null differ diff --git a/server/tests/fixtures/thumbnail.jpg b/server/tests/fixtures/thumbnail.jpg deleted file mode 100644 index 020853780..000000000 Binary files a/server/tests/fixtures/thumbnail.jpg and /dev/null differ diff --git a/server/tests/fixtures/thumbnail.png b/server/tests/fixtures/thumbnail.png deleted file mode 100644 index b331aba3b..000000000 Binary files a/server/tests/fixtures/thumbnail.png and /dev/null differ diff --git a/server/tests/fixtures/video_short1-preview.webm.jpg b/server/tests/fixtures/video_short1-preview.webm.jpg index f5659f6a8..15454942d 100644 Binary files a/server/tests/fixtures/video_short1-preview.webm.jpg and b/server/tests/fixtures/video_short1-preview.webm.jpg differ diff --git a/server/tests/fixtures/video_short1.webm.jpg b/server/tests/fixtures/video_short1.webm.jpg index 26a697daa..b2740d73d 100644 Binary files a/server/tests/fixtures/video_short1.webm.jpg and b/server/tests/fixtures/video_short1.webm.jpg differ diff --git a/server/tests/fixtures/video_short2.webm.jpg b/server/tests/fixtures/video_short2.webm.jpg index 020853780..afe476c7f 100644 Binary files a/server/tests/fixtures/video_short2.webm.jpg and b/server/tests/fixtures/video_short2.webm.jpg differ diff --git a/server/tests/helpers/image.ts b/server/tests/helpers/image.ts index 530c9bacd..6021ffc48 100644 --- a/server/tests/helpers/image.ts +++ b/server/tests/helpers/image.ts @@ -35,28 +35,28 @@ describe('Image helpers', function () { const thumbnailSize = { width: 280, height: 157 } it('Should skip processing if the source image is okay', async function () { - const input = buildAbsoluteFixturePath('thumbnail.jpg') + const input = buildAbsoluteFixturePath('custom-thumbnail.jpg') await processImage({ path: input, destination: imageDestJPG, newSize: thumbnailSize, keepOriginal: true }) await checkBuffers(input, imageDestJPG, true) }) it('Should not skip processing if the source image does not have the appropriate extension', async function () { - const input = buildAbsoluteFixturePath('thumbnail.png') + const input = buildAbsoluteFixturePath('custom-thumbnail.png') await processImage({ path: input, destination: imageDestJPG, newSize: thumbnailSize, keepOriginal: true }) await checkBuffers(input, imageDestJPG, false) }) it('Should not skip processing if the source image does not have the appropriate size', async function () { - const input = buildAbsoluteFixturePath('preview.jpg') + const input = buildAbsoluteFixturePath('custom-preview.jpg') await processImage({ path: input, destination: imageDestJPG, newSize: thumbnailSize, keepOriginal: true }) await checkBuffers(input, imageDestJPG, false) }) it('Should not skip processing if the source image does not have the appropriate size', async function () { - const input = buildAbsoluteFixturePath('thumbnail-big.jpg') + const input = buildAbsoluteFixturePath('custom-thumbnail-big.jpg') await processImage({ path: input, destination: imageDestJPG, newSize: thumbnailSize, keepOriginal: true }) await checkBuffers(input, imageDestJPG, false) diff --git a/server/tests/shared/checks.ts b/server/tests/shared/checks.ts index feaef37c6..90179c6ac 100644 --- a/server/tests/shared/checks.ts +++ b/server/tests/shared/checks.ts @@ -61,6 +61,16 @@ async function testImageSize (url: string, imageName: string, imageHTTPPath: str expect(body.length).to.be.below(maxLength, 'the generated image is way larger than the recorded fixture') } +async function testImageGeneratedByFFmpeg (url: string, imageName: string, imageHTTPPath: string, extension = '.jpg') { + if (process.env.ENABLE_FFMPEG_THUMBNAIL_PIXEL_COMPARISON_TESTS !== 'true') { + console.log( + 'Pixel comparison of image generated by ffmpeg is disabled. ' + + 'You can enable it using `ENABLE_FFMPEG_THUMBNAIL_PIXEL_COMPARISON_TESTS=true env variable') + } + + return testImage(url, imageName, imageHTTPPath, extension) +} + async function testImage (url: string, imageName: string, imageHTTPPath: string, extension = '.jpg') { const res = await makeGetRequest({ url, @@ -148,6 +158,7 @@ async function checkVideoDuration (server: PeerTubeServer, videoUUID: string, du export { dateIsValid, + testImageGeneratedByFFmpeg, testImageSize, testImage, expectLogDoesNotContain, diff --git a/server/tests/shared/videos.ts b/server/tests/shared/videos.ts index 856fabd11..0bd161820 100644 --- a/server/tests/shared/videos.ts +++ b/server/tests/shared/videos.ts @@ -7,7 +7,7 @@ import { loadLanguages, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO import { getLowercaseExtension, pick, uuidRegex } from '@shared/core-utils' import { HttpStatusCode, VideoCaption, VideoDetails, VideoPrivacy, VideoResolution } from '@shared/models' import { makeRawRequest, PeerTubeServer, VideoEdit, waitJobs } from '@shared/server-commands' -import { dateIsValid, expectStartWith, testImage } from './checks' +import { dateIsValid, expectStartWith, testImageGeneratedByFFmpeg } from './checks' import { checkWebTorrentWorks } from './webtorrent' loadLanguages() @@ -197,11 +197,11 @@ async function completeVideoCheck (options: { expect(video.downloadEnabled).to.equal(attributes.downloadEnabled) expect(video.thumbnailPath).to.exist - await testImage(server.url, attributes.thumbnailfile || attributes.fixture, video.thumbnailPath) + await testImageGeneratedByFFmpeg(server.url, attributes.thumbnailfile || attributes.fixture, video.thumbnailPath) if (attributes.previewfile) { expect(video.previewPath).to.exist - await testImage(server.url, attributes.previewfile, video.previewPath) + await testImageGeneratedByFFmpeg(server.url, attributes.previewfile, video.previewPath) } await completeWebVideoFilesCheck({ server, originServer, videoUUID: video.uuid, ...pick(attributes, [ 'fixture', 'files' ]) }) diff --git a/shared/server-commands/server/server.ts b/shared/server-commands/server/server.ts index 6aa4296b0..9a07eb36f 100644 --- a/shared/server-commands/server/server.ts +++ b/shared/server-commands/server/server.ts @@ -237,7 +237,7 @@ export class PeerTubeServer { } // Share the environment - const env = Object.create(process.env) + const env = { ...process.env } env['NODE_ENV'] = 'test' env['NODE_APP_INSTANCE'] = this.internalServerNumber.toString() env['NODE_CONFIG'] = JSON.stringify(configOverride) diff --git a/shared/server-commands/videos/video-studio-command.ts b/shared/server-commands/videos/video-studio-command.ts index 9fe467cc2..675cd84b7 100644 --- a/shared/server-commands/videos/video-studio-command.ts +++ b/shared/server-commands/videos/video-studio-command.ts @@ -25,7 +25,7 @@ export class VideoStudioCommand extends AbstractCommand { { name: 'add-watermark', options: { - file: 'thumbnail.png' + file: 'custom-thumbnail.png' } }, diff --git a/support/doc/development/tests.md b/support/doc/development/tests.md index e3a65c35f..1c2589c8a 100644 --- a/support/doc/development/tests.md +++ b/support/doc/development/tests.md @@ -71,6 +71,7 @@ Some env variables can be defined to disable/enable some tests: * `ENABLE_OBJECT_STORAGE_TESTS=true`: enable object storage tests (needs `chocobozzz/s3-ninja` container first) * `AKISMET_KEY`: specify an Akismet key to test akismet external PeerTube plugin * `OBJECT_STORAGE_SCALEWAY_KEY_ID` and `OBJECT_STORAGE_SCALEWAY_ACCESS_KEY`: specify Scaleway API keys to test object storage ACL (not supported by our `chocobozzz/s3-ninja` container) + * `ENABLE_FFMPEG_THUMBNAIL_PIXEL_COMPARISON_TESTS=true`: enable pixel comparison on images generated by ffmpeg. Disabled by default because a custom ffmpeg version may fails the tests ### Debug server logs