diff --git a/packages/tests/src/api/transcoding/video-studio.ts b/packages/tests/src/api/transcoding/video-studio.ts index 0a69e3ac1..48f514f9a 100644 --- a/packages/tests/src/api/transcoding/video-studio.ts +++ b/packages/tests/src/api/transcoding/video-studio.ts @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { getAllFiles } from '@peertube/peertube-core-utils' +import { getAllFiles, getHLS } from '@peertube/peertube-core-utils' import { VideoStudioTask } from '@peertube/peertube-models' import { areMockObjectStorageTestsDisabled } from '@peertube/peertube-node-utils' import { @@ -111,6 +111,41 @@ describe('Test video studio', function () { await checkVideoDuration(server, videoUUID, 4) } }) + + it('Should cut start/end of the audio', async function () { + this.timeout(120_000) + + await servers[0].config.enableMinimumTranscoding({ splitAudioAndVideo: true }) + await renewVideo('video_short1.webm') + await servers[0].config.enableMinimumTranscoding() + + const video = await servers[0].videos.get({ id: videoUUID }) + for (const file of video.files) { + if (file.resolution.id === 0) continue + + await servers[0].videos.removeWebVideoFile({ fileId: file.id, videoId: videoUUID }) + } + + for (const file of getHLS(video).files) { + if (file.resolution.id === 0) continue + + await servers[0].videos.removeHLSFile({ fileId: file.id, videoId: videoUUID }) + } + + await createTasks([ + { + name: 'cut', + options: { + start: 2, + end: 6 + } + } + ]) + + for (const server of servers) { + await checkVideoDuration(server, videoUUID, 4) + } + }) }) describe('Intro/Outro', function () { @@ -261,6 +296,7 @@ describe('Test video studio', function () { }) describe('Complex tasks', function () { + it('Should run a complex task', async function () { this.timeout(240_000) await renewVideo() diff --git a/server/core/middlewares/validators/videos/video-studio.ts b/server/core/middlewares/validators/videos/video-studio.ts index 6f6f2dc54..7723b15ae 100644 --- a/server/core/middlewares/validators/videos/video-studio.ts +++ b/server/core/middlewares/validators/videos/video-studio.ts @@ -33,10 +33,14 @@ const videoStudioAddEditionValidator = [ } if (areValidationErrors(req, res)) return cleanUpReqFiles(req) + if (!await doesVideoExist(req.params.videoId, res)) return cleanUpReqFiles(req) const body: VideoStudioCreateEdition = req.body const files = req.files as Express.Multer.File[] + const video = res.locals.videoAll + const videoIsAudio = video.hasAudio() && !video.hasVideo() + for (let i = 0; i < body.tasks.length; i++) { const task = body.tasks[i] @@ -49,6 +53,17 @@ const videoStudioAddEditionValidator = [ return cleanUpReqFiles(req) } + if (videoIsAudio) { + if (task.name === 'add-intro' || task.name === 'add-outro' || task.name === 'add-watermark') { + res.fail({ + status: HttpStatusCode.BAD_REQUEST_400, + message: `Task ${task.name} is invalid: video does not contain a video stream` + }) + + return cleanUpReqFiles(req) + } + } + if (task.name === 'add-intro' || task.name === 'add-outro') { const filePath = getTaskFileFromReq(files, i).path @@ -56,7 +71,7 @@ const videoStudioAddEditionValidator = [ if (await isAudioFile(filePath)) { res.fail({ status: HttpStatusCode.BAD_REQUEST_400, - message: `Task ${task.name} is invalid: file does not contain a video stream` + message: `Task ${task.name} is invalid: input file does not contain a video stream` }) return cleanUpReqFiles(req) @@ -64,9 +79,6 @@ const videoStudioAddEditionValidator = [ } } - if (!await doesVideoExist(req.params.videoId, res)) return cleanUpReqFiles(req) - - const video = res.locals.videoAll if (!checkVideoFileCanBeEdited(video, res)) return cleanUpReqFiles(req) const user = res.locals.oauth.token.User diff --git a/server/core/models/video/video.ts b/server/core/models/video/video.ts index 6de1f6627..2cfd1caac 100644 --- a/server/core/models/video/video.ts +++ b/server/core/models/video/video.ts @@ -1738,7 +1738,13 @@ export class VideoModel extends SequelizeModel { getMaxQualityAudioAndVideoFiles (this: T) { const videoFile = this.getMaxQualityFile(VideoFileStream.VIDEO) - if (!videoFile) return { videoFile: undefined } + + if (!videoFile) { + const audioOnly = this.getMaxQualityFile(VideoFileStream.AUDIO) + if (audioOnly) return { videoFile: audioOnly } + + return { videoFile: undefined } + } // File also has audio, we can return it if (videoFile.hasAudio()) return { videoFile } @@ -1759,7 +1765,7 @@ export class VideoModel extends SequelizeModel { getMaxQualityBytes (this: T) { const { videoFile, separatedAudioFile } = this.getMaxQualityAudioAndVideoFiles() - let size = videoFile.size + let size = videoFile?.size || 0 if (separatedAudioFile) size += separatedAudioFile.size return size @@ -1801,6 +1807,10 @@ export class VideoModel extends SequelizeModel { return !!this.getMaxQualityFile(VideoFileStream.AUDIO) } + hasVideo () { + return !!this.getMaxQualityFile(VideoFileStream.VIDEO) + } + // --------------------------------------------------------------------------- getWebVideoFileMinResolution (this: T, resolution: number): MVideoFileVideo {