From 536598cfafab1c5e24e881db1c528489f804fb6b Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 16 May 2019 16:55:34 +0200 Subject: Add audio support in upload --- server/lib/files-cache/videos-preview-cache.ts | 2 +- server/lib/job-queue/handlers/video-file-import.ts | 4 +- server/lib/job-queue/handlers/video-import.ts | 1 + server/lib/job-queue/handlers/video-transcoding.ts | 56 ++++++++++---- server/lib/thumbnail.ts | 8 +- server/lib/video-transcoding.ts | 88 ++++++++++++++++------ 6 files changed, 114 insertions(+), 45 deletions(-) (limited to 'server/lib') diff --git a/server/lib/files-cache/videos-preview-cache.ts b/server/lib/files-cache/videos-preview-cache.ts index 14be7f24a..a68619d07 100644 --- a/server/lib/files-cache/videos-preview-cache.ts +++ b/server/lib/files-cache/videos-preview-cache.ts @@ -21,7 +21,7 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache { const video = await VideoModel.loadByUUIDWithFile(videoUUID) if (!video) return undefined - if (video.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreview().filename) } + if (video.isOwned()) return { isOwned: true, path: video.getPreview().getPath() } return this.loadRemoteFile(videoUUID) } diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts index 921d9a083..8cacb0ef3 100644 --- a/server/lib/job-queue/handlers/video-file-import.ts +++ b/server/lib/job-queue/handlers/video-file-import.ts @@ -1,7 +1,7 @@ import * as Bull from 'bull' import { logger } from '../../../helpers/logger' import { VideoModel } from '../../../models/video/video' -import { publishVideoIfNeeded } from './video-transcoding' +import { publishNewResolutionIfNeeded } from './video-transcoding' import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' import { copy, stat } from 'fs-extra' import { VideoFileModel } from '../../../models/video/video-file' @@ -25,7 +25,7 @@ async function processVideoFileImport (job: Bull.Job) { await updateVideoFile(video, payload.filePath) - await publishVideoIfNeeded(video) + await publishNewResolutionIfNeeded(video) return video } diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 1650916a6..50e159245 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts @@ -209,6 +209,7 @@ async function processFile (downloader: () => Promise, videoImport: Vide if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { // Put uuid because we don't have id auto incremented for now const dataInput = { + type: 'optimize' as 'optimize', videoUUID: videoImportUpdated.Video.uuid, isNewVideo: true } diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 48cac517e..e9b84ecd6 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts @@ -8,18 +8,39 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils' import { sequelizeTypescript } from '../../../initializers' import * as Bluebird from 'bluebird' import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' -import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile } from '../../video-transcoding' +import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding' import { Notifier } from '../../notifier' import { CONFIG } from '../../../initializers/config' -export type VideoTranscodingPayload = { +interface BaseTranscodingPayload { videoUUID: string - resolution?: VideoResolution isNewVideo?: boolean +} + +interface HLSTranscodingPayload extends BaseTranscodingPayload { + type: 'hls' + isPortraitMode?: boolean + resolution: VideoResolution +} + +interface NewResolutionTranscodingPayload extends BaseTranscodingPayload { + type: 'new-resolution' isPortraitMode?: boolean - generateHlsPlaylist?: boolean + resolution: VideoResolution +} + +interface MergeAudioTranscodingPayload extends BaseTranscodingPayload { + type: 'merge-audio' + resolution: VideoResolution +} + +interface OptimizeTranscodingPayload extends BaseTranscodingPayload { + type: 'optimize' } +export type VideoTranscodingPayload = HLSTranscodingPayload | NewResolutionTranscodingPayload + | OptimizeTranscodingPayload | MergeAudioTranscodingPayload + async function processVideoTranscoding (job: Bull.Job) { const payload = job.data as VideoTranscodingPayload logger.info('Processing video file in job %d.', job.id) @@ -31,14 +52,18 @@ async function processVideoTranscoding (job: Bull.Job) { return undefined } - if (payload.generateHlsPlaylist) { + if (payload.type === 'hls') { await generateHlsPlaylist(video, payload.resolution, payload.isPortraitMode || false) await retryTransactionWrapper(onHlsPlaylistGenerationSuccess, video) - } else if (payload.resolution) { // Transcoding in other resolution + } else if (payload.type === 'new-resolution') { await transcodeOriginalVideofile(video, payload.resolution, payload.isPortraitMode || false) - await retryTransactionWrapper(publishVideoIfNeeded, video, payload) + await retryTransactionWrapper(publishNewResolutionIfNeeded, video, payload) + } else if (payload.type === 'merge-audio') { + await mergeAudioVideofile(video, payload.resolution) + + await retryTransactionWrapper(publishNewResolutionIfNeeded, video, payload) } else { await optimizeVideofile(video) @@ -62,7 +87,7 @@ async function onHlsPlaylistGenerationSuccess (video: VideoModel) { }) } -async function publishVideoIfNeeded (video: VideoModel, payload?: VideoTranscodingPayload) { +async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) { const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { // Maybe the video changed in database, refresh it let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) @@ -94,7 +119,7 @@ async function publishVideoIfNeeded (video: VideoModel, payload?: VideoTranscodi await createHlsJobIfEnabled(payload) } -async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: VideoTranscodingPayload) { +async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: OptimizeTranscodingPayload) { if (videoArg === undefined) return undefined // Outside the transaction (IO on disk) @@ -120,6 +145,7 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: Video for (const resolution of resolutionsEnabled) { const dataInput = { + type: 'new-resolution' as 'new-resolution', videoUUID: videoDatabase.uuid, resolution } @@ -149,27 +175,27 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: Video if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase) - await createHlsJobIfEnabled(Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution })) + const hlsPayload = Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution }) + await createHlsJobIfEnabled(hlsPayload) } // --------------------------------------------------------------------------- export { processVideoTranscoding, - publishVideoIfNeeded + publishNewResolutionIfNeeded } // --------------------------------------------------------------------------- -function createHlsJobIfEnabled (payload?: VideoTranscodingPayload) { +function createHlsJobIfEnabled (payload?: { videoUUID: string, resolution: number, isPortraitMode?: boolean }) { // Generate HLS playlist? if (payload && CONFIG.TRANSCODING.HLS.ENABLED) { const hlsTranscodingPayload = { + type: 'hls' as 'hls', videoUUID: payload.videoUUID, resolution: payload.resolution, - isPortraitMode: payload.isPortraitMode, - - generateHlsPlaylist: true + isPortraitMode: payload.isPortraitMode } return JobQueue.Instance.createJob({ type: 'video-transcoding', payload: hlsTranscodingPayload }) diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts index 950b14c3b..18bdcded4 100644 --- a/server/lib/thumbnail.ts +++ b/server/lib/thumbnail.ts @@ -1,7 +1,7 @@ import { VideoFileModel } from '../models/video/video-file' import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' import { CONFIG } from '../initializers/config' -import { PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants' +import { PREVIEWS_SIZE, THUMBNAILS_SIZE, ASSETS_PATH } from '../initializers/constants' import { VideoModel } from '../models/video/video' import { ThumbnailModel } from '../models/video/thumbnail' import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' @@ -45,8 +45,10 @@ function createVideoMiniatureFromExisting (inputPath: string, video: VideoModel, function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) { const input = video.getVideoFilePath(videoFile) - const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type) - const thumbnailCreator = () => generateImageFromVideoFile(input, basePath, filename, { height, width }) + const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type) + const thumbnailCreator = videoFile.isAudio() + ? () => processImage(ASSETS_PATH.DEFAULT_AUDIO_BACKGROUND, outputPath, { width, height }, true) + : () => generateImageFromVideoFile(input, basePath, filename, { height, width }) return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail }) } diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index d6b6b251a..8d786e0ef 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts @@ -1,6 +1,6 @@ import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants' import { join } from 'path' -import { getVideoFileFPS, transcode, canDoQuickTranscode } from '../helpers/ffmpeg-utils' +import { canDoQuickTranscode, getVideoFileFPS, transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils' import { ensureDir, move, remove, stat } from 'fs-extra' import { logger } from '../helpers/logger' import { VideoResolution } from '../../shared/models/videos' @@ -23,13 +23,15 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile)) const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) - const doQuickTranscode = await(canDoQuickTranscode(videoInputPath)) + const transcodeType: TranscodeOptionsType = await canDoQuickTranscode(videoInputPath) + ? 'quick-transcode' + : 'video' - const transcodeOptions = { + const transcodeOptions: TranscodeOptions = { + type: transcodeType as any, // FIXME: typing issue inputPath: videoInputPath, outputPath: videoTranscodedPath, - resolution: inputVideoFile.resolution, - doQuickTranscode + resolution: inputVideoFile.resolution } // Could be very long! @@ -39,19 +41,11 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi await remove(videoInputPath) // Important to do this before getVideoFilename() to take in account the new file extension - inputVideoFile.set('extname', newExtname) - - const stats = await stat(videoTranscodedPath) - const fps = await getVideoFileFPS(videoTranscodedPath) + inputVideoFile.extname = newExtname const videoOutputPath = video.getVideoFilePath(inputVideoFile) - await move(videoTranscodedPath, videoOutputPath) - inputVideoFile.set('size', stats.size) - inputVideoFile.set('fps', fps) - - await video.createTorrentAndSetInfoHash(inputVideoFile) - await inputVideoFile.save() + await onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) } catch (err) { // Auto destruction... video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err })) @@ -81,6 +75,7 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR const videoTranscodedPath = join(transcodeDirectory, video.getVideoFilename(newVideoFile)) const transcodeOptions = { + type: 'video' as 'video', inputPath: videoInputPath, outputPath: videoTranscodedPath, resolution, @@ -89,19 +84,37 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR await transcode(transcodeOptions) - const stats = await stat(videoTranscodedPath) - const fps = await getVideoFileFPS(videoTranscodedPath) + return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) +} + +async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) { + const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR + const transcodeDirectory = CONFIG.STORAGE.TMP_DIR + const newExtname = '.mp4' + + const inputVideoFile = video.getOriginalFile() - await move(videoTranscodedPath, videoOutputPath) + const audioInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile())) + const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) - newVideoFile.set('size', stats.size) - newVideoFile.set('fps', fps) + const transcodeOptions = { + type: 'merge-audio' as 'merge-audio', + inputPath: video.getPreview().getPath(), + outputPath: videoTranscodedPath, + audioPath: audioInputPath, + resolution + } - await video.createTorrentAndSetInfoHash(newVideoFile) + await transcode(transcodeOptions) - await newVideoFile.save() + await remove(audioInputPath) - video.VideoFiles.push(newVideoFile) + // Important to do this before getVideoFilename() to take in account the new file extension + inputVideoFile.extname = newExtname + + const videoOutputPath = video.getVideoFilePath(inputVideoFile) + + return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) } async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { @@ -112,6 +125,7 @@ async function generateHlsPlaylist (video: VideoModel, resolution: VideoResoluti const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) const transcodeOptions = { + type: 'hls' as 'hls', inputPath: videoInputPath, outputPath, resolution, @@ -140,8 +154,34 @@ async function generateHlsPlaylist (video: VideoModel, resolution: VideoResoluti }) } +// --------------------------------------------------------------------------- + export { generateHlsPlaylist, optimizeVideofile, - transcodeOriginalVideofile + transcodeOriginalVideofile, + mergeAudioVideofile +} + +// --------------------------------------------------------------------------- + +async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) { + const stats = await stat(transcodingPath) + const fps = await getVideoFileFPS(transcodingPath) + + await move(transcodingPath, outputPath) + + videoFile.set('size', stats.size) + videoFile.set('fps', fps) + + await video.createTorrentAndSetInfoHash(videoFile) + + const updatedVideoFile = await videoFile.save() + + // Add it if this is a new created file + if (video.VideoFiles.some(f => f.id === videoFile.id) === false) { + video.VideoFiles.push(updatedVideoFile) + } + + return video } -- cgit v1.2.3