diff options
-rwxr-xr-x | scripts/create-transcoding-job.ts | 8 | ||||
-rw-r--r-- | server/controllers/api/videos/transcoding.ts | 4 | ||||
-rw-r--r-- | server/lib/job-queue/handlers/video-transcoding.ts | 25 | ||||
-rw-r--r-- | server/models/video/video.ts | 16 | ||||
-rw-r--r-- | shared/models/server/job.model.ts | 9 |
5 files changed, 46 insertions, 16 deletions
diff --git a/scripts/create-transcoding-job.ts b/scripts/create-transcoding-job.ts index d462fbf33..95e1e66cf 100755 --- a/scripts/create-transcoding-job.ts +++ b/scripts/create-transcoding-job.ts | |||
@@ -60,7 +60,11 @@ async function run () { | |||
60 | type: 'new-resolution-to-hls', | 60 | type: 'new-resolution-to-hls', |
61 | videoUUID: video.uuid, | 61 | videoUUID: video.uuid, |
62 | resolution, | 62 | resolution, |
63 | |||
64 | // FIXME: check the file has audio and is not in portrait mode | ||
63 | isPortraitMode: false, | 65 | isPortraitMode: false, |
66 | hasAudio: true, | ||
67 | |||
64 | copyCodecs: false, | 68 | copyCodecs: false, |
65 | isNewVideo: false, | 69 | isNewVideo: false, |
66 | isMaxQuality: maxResolution === resolution, | 70 | isMaxQuality: maxResolution === resolution, |
@@ -72,6 +76,10 @@ async function run () { | |||
72 | dataInput.push({ | 76 | dataInput.push({ |
73 | type: 'new-resolution-to-webtorrent', | 77 | type: 'new-resolution-to-webtorrent', |
74 | videoUUID: video.uuid, | 78 | videoUUID: video.uuid, |
79 | |||
80 | // FIXME: check the file has audio | ||
81 | hasAudio: true, | ||
82 | |||
75 | isNewVideo: false, | 83 | isNewVideo: false, |
76 | resolution: parseInt(options.resolution) | 84 | resolution: parseInt(options.resolution) |
77 | }) | 85 | }) |
diff --git a/server/controllers/api/videos/transcoding.ts b/server/controllers/api/videos/transcoding.ts index dd6fbd3de..388689c8a 100644 --- a/server/controllers/api/videos/transcoding.ts +++ b/server/controllers/api/videos/transcoding.ts | |||
@@ -29,7 +29,7 @@ async function createTranscoding (req: express.Request, res: express.Response) { | |||
29 | 29 | ||
30 | const body: VideoTranscodingCreate = req.body | 30 | const body: VideoTranscodingCreate = req.body |
31 | 31 | ||
32 | const { resolution: maxResolution, isPortraitMode } = await video.getMaxQualityResolution() | 32 | const { resolution: maxResolution, isPortraitMode, audioStream } = await video.getMaxQualityFileInfo() |
33 | const resolutions = computeLowerResolutionsToTranscode(maxResolution, 'vod').concat([ maxResolution ]) | 33 | const resolutions = computeLowerResolutionsToTranscode(maxResolution, 'vod').concat([ maxResolution ]) |
34 | 34 | ||
35 | video.state = VideoState.TO_TRANSCODE | 35 | video.state = VideoState.TO_TRANSCODE |
@@ -42,6 +42,7 @@ async function createTranscoding (req: express.Request, res: express.Response) { | |||
42 | videoUUID: video.uuid, | 42 | videoUUID: video.uuid, |
43 | resolution, | 43 | resolution, |
44 | isPortraitMode, | 44 | isPortraitMode, |
45 | hasAudio: !!audioStream, | ||
45 | copyCodecs: false, | 46 | copyCodecs: false, |
46 | isNewVideo: false, | 47 | isNewVideo: false, |
47 | autoDeleteWebTorrentIfNeeded: false, | 48 | autoDeleteWebTorrentIfNeeded: false, |
@@ -53,6 +54,7 @@ async function createTranscoding (req: express.Request, res: express.Response) { | |||
53 | videoUUID: video.uuid, | 54 | videoUUID: video.uuid, |
54 | isNewVideo: false, | 55 | isNewVideo: false, |
55 | resolution: resolution, | 56 | resolution: resolution, |
57 | hasAudio: !!audioStream, | ||
56 | isPortraitMode | 58 | isPortraitMode |
57 | }) | 59 | }) |
58 | } | 60 | } |
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index ef3abcbcd..02902b0b8 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts | |||
@@ -6,11 +6,13 @@ import { moveToFailedTranscodingState, moveToNextState } from '@server/lib/video | |||
6 | import { UserModel } from '@server/models/user/user' | 6 | import { UserModel } from '@server/models/user/user' |
7 | import { VideoJobInfoModel } from '@server/models/video/video-job-info' | 7 | import { VideoJobInfoModel } from '@server/models/video/video-job-info' |
8 | import { MUser, MUserId, MVideo, MVideoFullLight, MVideoWithFile } from '@server/types/models' | 8 | import { MUser, MUserId, MVideo, MVideoFullLight, MVideoWithFile } from '@server/types/models' |
9 | import { pick } from '@shared/core-utils' | ||
9 | import { | 10 | import { |
10 | HLSTranscodingPayload, | 11 | HLSTranscodingPayload, |
11 | MergeAudioTranscodingPayload, | 12 | MergeAudioTranscodingPayload, |
12 | NewResolutionTranscodingPayload, | 13 | NewResolutionTranscodingPayload, |
13 | OptimizeTranscodingPayload, | 14 | OptimizeTranscodingPayload, |
15 | VideoResolution, | ||
14 | VideoTranscodingPayload | 16 | VideoTranscodingPayload |
15 | } from '@shared/models' | 17 | } from '@shared/models' |
16 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 18 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
@@ -159,6 +161,7 @@ async function onHlsPlaylistGeneration (video: MVideoFullLight, user: MUser, pay | |||
159 | user, | 161 | user, |
160 | videoFileResolution: payload.resolution, | 162 | videoFileResolution: payload.resolution, |
161 | isPortraitMode: payload.isPortraitMode, | 163 | isPortraitMode: payload.isPortraitMode, |
164 | hasAudio: payload.hasAudio, | ||
162 | isNewVideo: payload.isNewVideo ?? true, | 165 | isNewVideo: payload.isNewVideo ?? true, |
163 | type: 'hls' | 166 | type: 'hls' |
164 | }) | 167 | }) |
@@ -174,7 +177,7 @@ async function onVideoFirstWebTorrentTranscoding ( | |||
174 | transcodeType: TranscodeOptionsType, | 177 | transcodeType: TranscodeOptionsType, |
175 | user: MUserId | 178 | user: MUserId |
176 | ) { | 179 | ) { |
177 | const { resolution, isPortraitMode } = await videoArg.getMaxQualityResolution() | 180 | const { resolution, isPortraitMode, audioStream } = await videoArg.getMaxQualityFileInfo() |
178 | 181 | ||
179 | // Maybe the video changed in database, refresh it | 182 | // Maybe the video changed in database, refresh it |
180 | const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid) | 183 | const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid) |
@@ -186,6 +189,7 @@ async function onVideoFirstWebTorrentTranscoding ( | |||
186 | ...payload, | 189 | ...payload, |
187 | 190 | ||
188 | isPortraitMode, | 191 | isPortraitMode, |
192 | hasAudio: !!audioStream, | ||
189 | resolution: videoDatabase.getMaxQualityFile().resolution, | 193 | resolution: videoDatabase.getMaxQualityFile().resolution, |
190 | // If we quick transcoded original file, force transcoding for HLS to avoid some weird playback issues | 194 | // If we quick transcoded original file, force transcoding for HLS to avoid some weird playback issues |
191 | copyCodecs: transcodeType !== 'quick-transcode', | 195 | copyCodecs: transcodeType !== 'quick-transcode', |
@@ -196,6 +200,7 @@ async function onVideoFirstWebTorrentTranscoding ( | |||
196 | video: videoDatabase, | 200 | video: videoDatabase, |
197 | user, | 201 | user, |
198 | videoFileResolution: resolution, | 202 | videoFileResolution: resolution, |
203 | hasAudio: !!audioStream, | ||
199 | isPortraitMode, | 204 | isPortraitMode, |
200 | type: 'webtorrent', | 205 | type: 'webtorrent', |
201 | isNewVideo: payload.isNewVideo ?? true | 206 | isNewVideo: payload.isNewVideo ?? true |
@@ -214,7 +219,7 @@ async function onNewWebTorrentFileResolution ( | |||
214 | user: MUserId, | 219 | user: MUserId, |
215 | payload: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload | 220 | payload: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload |
216 | ) { | 221 | ) { |
217 | await createHlsJobIfEnabled(user, { ...payload, copyCodecs: true, isMaxQuality: false }) | 222 | await createHlsJobIfEnabled(user, { hasAudio: true, copyCodecs: true, isMaxQuality: false, ...payload }) |
218 | await VideoJobInfoModel.decrease(video.uuid, 'pendingTranscode') | 223 | await VideoJobInfoModel.decrease(video.uuid, 'pendingTranscode') |
219 | 224 | ||
220 | await retryTransactionWrapper(moveToNextState, video, payload.isNewVideo) | 225 | await retryTransactionWrapper(moveToNextState, video, payload.isNewVideo) |
@@ -225,6 +230,7 @@ async function onNewWebTorrentFileResolution ( | |||
225 | async function createHlsJobIfEnabled (user: MUserId, payload: { | 230 | async function createHlsJobIfEnabled (user: MUserId, payload: { |
226 | videoUUID: string | 231 | videoUUID: string |
227 | resolution: number | 232 | resolution: number |
233 | hasAudio: boolean | ||
228 | isPortraitMode?: boolean | 234 | isPortraitMode?: boolean |
229 | copyCodecs: boolean | 235 | copyCodecs: boolean |
230 | isMaxQuality: boolean | 236 | isMaxQuality: boolean |
@@ -238,13 +244,9 @@ async function createHlsJobIfEnabled (user: MUserId, payload: { | |||
238 | 244 | ||
239 | const hlsTranscodingPayload: HLSTranscodingPayload = { | 245 | const hlsTranscodingPayload: HLSTranscodingPayload = { |
240 | type: 'new-resolution-to-hls', | 246 | type: 'new-resolution-to-hls', |
241 | videoUUID: payload.videoUUID, | ||
242 | resolution: payload.resolution, | ||
243 | isPortraitMode: payload.isPortraitMode, | ||
244 | copyCodecs: payload.copyCodecs, | ||
245 | isMaxQuality: payload.isMaxQuality, | ||
246 | autoDeleteWebTorrentIfNeeded: true, | 247 | autoDeleteWebTorrentIfNeeded: true, |
247 | isNewVideo: payload.isNewVideo | 248 | |
249 | ...pick(payload, [ 'videoUUID', 'resolution', 'isPortraitMode', 'copyCodecs', 'isMaxQuality', 'isNewVideo', 'hasAudio' ]) | ||
248 | } | 250 | } |
249 | 251 | ||
250 | await addTranscodingJob(hlsTranscodingPayload, jobOptions) | 252 | await addTranscodingJob(hlsTranscodingPayload, jobOptions) |
@@ -257,16 +259,19 @@ async function createLowerResolutionsJobs (options: { | |||
257 | user: MUserId | 259 | user: MUserId |
258 | videoFileResolution: number | 260 | videoFileResolution: number |
259 | isPortraitMode: boolean | 261 | isPortraitMode: boolean |
262 | hasAudio: boolean | ||
260 | isNewVideo: boolean | 263 | isNewVideo: boolean |
261 | type: 'hls' | 'webtorrent' | 264 | type: 'hls' | 'webtorrent' |
262 | }) { | 265 | }) { |
263 | const { video, user, videoFileResolution, isPortraitMode, isNewVideo, type } = options | 266 | const { video, user, videoFileResolution, isPortraitMode, isNewVideo, hasAudio, type } = options |
264 | 267 | ||
265 | // Create transcoding jobs if there are enabled resolutions | 268 | // Create transcoding jobs if there are enabled resolutions |
266 | const resolutionsEnabled = computeLowerResolutionsToTranscode(videoFileResolution, 'vod') | 269 | const resolutionsEnabled = computeLowerResolutionsToTranscode(videoFileResolution, 'vod') |
267 | const resolutionCreated: string[] = [] | 270 | const resolutionCreated: string[] = [] |
268 | 271 | ||
269 | for (const resolution of resolutionsEnabled) { | 272 | for (const resolution of resolutionsEnabled) { |
273 | if (resolution === VideoResolution.H_NOVIDEO && hasAudio === false) continue | ||
274 | |||
270 | let dataInput: VideoTranscodingPayload | 275 | let dataInput: VideoTranscodingPayload |
271 | 276 | ||
272 | if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED && type === 'webtorrent') { | 277 | if (CONFIG.TRANSCODING.WEBTORRENT.ENABLED && type === 'webtorrent') { |
@@ -276,6 +281,7 @@ async function createLowerResolutionsJobs (options: { | |||
276 | videoUUID: video.uuid, | 281 | videoUUID: video.uuid, |
277 | resolution, | 282 | resolution, |
278 | isPortraitMode, | 283 | isPortraitMode, |
284 | hasAudio, | ||
279 | isNewVideo | 285 | isNewVideo |
280 | } | 286 | } |
281 | 287 | ||
@@ -288,6 +294,7 @@ async function createLowerResolutionsJobs (options: { | |||
288 | videoUUID: video.uuid, | 294 | videoUUID: video.uuid, |
289 | resolution, | 295 | resolution, |
290 | isPortraitMode, | 296 | isPortraitMode, |
297 | hasAudio, | ||
291 | copyCodecs: false, | 298 | copyCodecs: false, |
292 | isMaxQuality: false, | 299 | isMaxQuality: false, |
293 | autoDeleteWebTorrentIfNeeded: true, | 300 | autoDeleteWebTorrentIfNeeded: true, |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index e5077487a..12b937574 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -33,7 +33,7 @@ import { VideoPathManager } from '@server/lib/video-path-manager' | |||
33 | import { getServerActor } from '@server/models/application/application' | 33 | import { getServerActor } from '@server/models/application/application' |
34 | import { ModelCache } from '@server/models/model-cache' | 34 | import { ModelCache } from '@server/models/model-cache' |
35 | import { buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils' | 35 | import { buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils' |
36 | import { uuidToShort } from '@shared/extra-utils' | 36 | import { ffprobePromise, getAudioStream, uuidToShort } from '@shared/extra-utils' |
37 | import { | 37 | import { |
38 | ResultList, | 38 | ResultList, |
39 | ThumbnailType, | 39 | ThumbnailType, |
@@ -1678,12 +1678,20 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> { | |||
1678 | return peertubeTruncate(this.description, { length: maxLength }) | 1678 | return peertubeTruncate(this.description, { length: maxLength }) |
1679 | } | 1679 | } |
1680 | 1680 | ||
1681 | getMaxQualityResolution () { | 1681 | getMaxQualityFileInfo () { |
1682 | const file = this.getMaxQualityFile() | 1682 | const file = this.getMaxQualityFile() |
1683 | const videoOrPlaylist = file.getVideoOrStreamingPlaylist() | 1683 | const videoOrPlaylist = file.getVideoOrStreamingPlaylist() |
1684 | 1684 | ||
1685 | return VideoPathManager.Instance.makeAvailableVideoFile(file.withVideoOrPlaylist(videoOrPlaylist), originalFilePath => { | 1685 | return VideoPathManager.Instance.makeAvailableVideoFile(file.withVideoOrPlaylist(videoOrPlaylist), async originalFilePath => { |
1686 | return getVideoFileResolution(originalFilePath) | 1686 | const probe = await ffprobePromise(originalFilePath) |
1687 | |||
1688 | const { audioStream } = await getAudioStream(originalFilePath, probe) | ||
1689 | |||
1690 | return { | ||
1691 | audioStream, | ||
1692 | |||
1693 | ...await getVideoFileResolution(originalFilePath, probe) | ||
1694 | } | ||
1687 | }) | 1695 | }) |
1688 | } | 1696 | } |
1689 | 1697 | ||
diff --git a/shared/models/server/job.model.ts b/shared/models/server/job.model.ts index ecc960da5..8a69d11fa 100644 --- a/shared/models/server/job.model.ts +++ b/shared/models/server/job.model.ts | |||
@@ -103,18 +103,23 @@ interface BaseTranscodingPayload { | |||
103 | 103 | ||
104 | export interface HLSTranscodingPayload extends BaseTranscodingPayload { | 104 | export interface HLSTranscodingPayload extends BaseTranscodingPayload { |
105 | type: 'new-resolution-to-hls' | 105 | type: 'new-resolution-to-hls' |
106 | isPortraitMode?: boolean | ||
107 | resolution: VideoResolution | 106 | resolution: VideoResolution |
108 | copyCodecs: boolean | 107 | copyCodecs: boolean |
109 | 108 | ||
109 | hasAudio: boolean | ||
110 | isPortraitMode?: boolean | ||
111 | |||
110 | autoDeleteWebTorrentIfNeeded: boolean | 112 | autoDeleteWebTorrentIfNeeded: boolean |
111 | isMaxQuality: boolean | 113 | isMaxQuality: boolean |
112 | } | 114 | } |
113 | 115 | ||
114 | export interface NewResolutionTranscodingPayload extends BaseTranscodingPayload { | 116 | export interface NewResolutionTranscodingPayload extends BaseTranscodingPayload { |
115 | type: 'new-resolution-to-webtorrent' | 117 | type: 'new-resolution-to-webtorrent' |
116 | isPortraitMode?: boolean | ||
117 | resolution: VideoResolution | 118 | resolution: VideoResolution |
119 | |||
120 | hasAudio: boolean | ||
121 | |||
122 | isPortraitMode?: boolean | ||
118 | } | 123 | } |
119 | 124 | ||
120 | export interface MergeAudioTranscodingPayload extends BaseTranscodingPayload { | 125 | export interface MergeAudioTranscodingPayload extends BaseTranscodingPayload { |