X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Flib%2Fjob-queue%2Fhandlers%2Fvideo-import.ts;h=9b5f2bb2ba32daeb5c74673ba9e1692fad05d776;hb=26d6bf6533023326fa017812cf31bbe20c752d36;hp=50e159245f0362f055eb045b0c4a0e5abbf5b1b8;hpb=a41b9da1a9ce49df82ea10c82de4c2fbc6d1b189;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 50e159245..9b5f2bb2b 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts @@ -1,41 +1,36 @@ import * as Bull from 'bull' -import { logger } from '../../../helpers/logger' -import { downloadYoutubeDLVideo } from '../../../helpers/youtube-dl' -import { VideoImportModel } from '../../../models/video/video-import' +import { move, remove, stat } from 'fs-extra' +import { extname } from 'path' +import { addOptimizeOrMergeAudioJob } from '@server/helpers/video' +import { isPostImportVideoAccepted } from '@server/lib/moderation' +import { Hooks } from '@server/lib/plugins/hooks' +import { getVideoFilePath } from '@server/lib/video-paths' +import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/types/models/video/video-import' +import { + VideoImportPayload, + VideoImportTorrentPayload, + VideoImportTorrentPayloadType, + VideoImportYoutubeDLPayload, + VideoImportYoutubeDLPayloadType, + VideoState +} from '../../../../shared' import { VideoImportState } from '../../../../shared/models/videos' +import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' import { getDurationFromVideoFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' -import { extname, join } from 'path' -import { VideoFileModel } from '../../../models/video/video-file' -import { VIDEO_IMPORT_TIMEOUT } from '../../../initializers/constants' -import { VideoState } from '../../../../shared' -import { JobQueue } from '../index' -import { federateVideoIfNeeded } from '../../activitypub' -import { VideoModel } from '../../../models/video/video' -import { downloadWebTorrentVideo } from '../../../helpers/webtorrent' +import { logger } from '../../../helpers/logger' import { getSecureTorrentName } from '../../../helpers/utils' -import { move, remove, stat } from 'fs-extra' -import { Notifier } from '../../notifier' +import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent' +import { downloadYoutubeDLVideo } from '../../../helpers/youtube-dl' import { CONFIG } from '../../../initializers/config' +import { VIDEO_IMPORT_TIMEOUT } from '../../../initializers/constants' import { sequelizeTypescript } from '../../../initializers/database' -import { ThumbnailModel } from '../../../models/video/thumbnail' -import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail' -import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' - -type VideoImportYoutubeDLPayload = { - type: 'youtube-dl' - videoImportId: number - - thumbnailUrl: string - downloadThumbnail: boolean - downloadPreview: boolean -} - -type VideoImportTorrentPayload = { - type: 'magnet-uri' | 'torrent-file' - videoImportId: number -} - -export type VideoImportPayload = VideoImportYoutubeDLPayload | VideoImportTorrentPayload +import { VideoModel } from '../../../models/video/video' +import { VideoFileModel } from '../../../models/video/video-file' +import { VideoImportModel } from '../../../models/video/video-import' +import { MThumbnail } from '../../../types/models/video/thumbnail' +import { federateVideoIfNeeded } from '../../activitypub/videos' +import { Notifier } from '../../notifier' +import { generateVideoMiniature } from '../../thumbnail' async function processVideoImport (job: Bull.Job) { const payload = job.data as VideoImportPayload @@ -58,11 +53,9 @@ async function processTorrentImport (job: Bull.Job, payload: VideoImportTorrentP const videoImport = await getVideoImportOrDie(payload.videoImportId) const options = { + type: payload.type, videoImportId: payload.videoImportId, - downloadThumbnail: false, - downloadPreview: false, - generateThumbnail: true, generatePreview: true } @@ -78,17 +71,14 @@ async function processYoutubeDLImport (job: Bull.Job, payload: VideoImportYoutub const videoImport = await getVideoImportOrDie(payload.videoImportId) const options = { + type: payload.type, videoImportId: videoImport.id, - downloadThumbnail: payload.downloadThumbnail, - downloadPreview: payload.downloadPreview, - thumbnailUrl: payload.thumbnailUrl, - - generateThumbnail: false, - generatePreview: false + generateThumbnail: payload.generateThumbnail, + generatePreview: payload.generatePreview } - return processFile(() => downloadYoutubeDLVideo(videoImport.targetUrl, VIDEO_IMPORT_TIMEOUT), videoImport, options) + return processFile(() => downloadYoutubeDLVideo(videoImport.targetUrl, payload.fileExt, VIDEO_IMPORT_TIMEOUT), videoImport, options) } async function getVideoImportOrDie (videoImportId: number) { @@ -101,16 +91,13 @@ async function getVideoImportOrDie (videoImportId: number) { } type ProcessFileOptions = { + type: VideoImportYoutubeDLPayloadType | VideoImportTorrentPayloadType videoImportId: number - downloadThumbnail: boolean - downloadPreview: boolean - thumbnailUrl?: string - generateThumbnail: boolean generatePreview: boolean } -async function processFile (downloader: () => Promise, videoImport: VideoImportModel, options: ProcessFileOptions) { +async function processFile (downloader: () => Promise, videoImport: MVideoImportDefault, options: ProcessFileOptions) { let tempVideoPath: string let videoDestFile: string let videoFile: VideoFileModel @@ -130,7 +117,7 @@ async function processFile (downloader: () => Promise, videoImport: Vide const fps = await getVideoFileFPS(tempVideoPath) const duration = await getDurationFromVideoFile(tempVideoPath) - // Create video file object in database + // Prepare video file object for creation in database const videoFileData = { extname: extname(tempVideoPath), resolution: videoFileResolution, @@ -139,41 +126,64 @@ async function processFile (downloader: () => Promise, videoImport: Vide videoId: videoImport.videoId } videoFile = new VideoFileModel(videoFileData) + + const hookName = options.type === 'youtube-dl' + ? 'filter:api.video.post-import-url.accept.result' + : 'filter:api.video.post-import-torrent.accept.result' + + // Check we accept this video + const acceptParameters = { + videoImport, + video: videoImport.Video, + videoFilePath: tempVideoPath, + videoFile, + user: videoImport.User + } + const acceptedResult = await Hooks.wrapFun(isPostImportVideoAccepted, acceptParameters, hookName) + + if (acceptedResult.accepted !== true) { + logger.info('Refused imported video.', { acceptedResult, acceptParameters }) + + videoImport.state = VideoImportState.REJECTED + await videoImport.save() + + throw new Error(acceptedResult.errorMessage) + } + + // Video is accepted, resuming preparation + const videoWithFiles = Object.assign(videoImport.Video, { VideoFiles: [ videoFile ], VideoStreamingPlaylists: [] }) // To clean files if the import fails - videoImport.Video.VideoFiles = [ videoFile ] + const videoImportWithFiles: MVideoImportDefaultFiles = Object.assign(videoImport, { Video: videoWithFiles }) // Move file - videoDestFile = join(CONFIG.STORAGE.VIDEOS_DIR, videoImport.Video.getVideoFilename(videoFile)) + videoDestFile = getVideoFilePath(videoImportWithFiles.Video, videoFile) await move(tempVideoPath, videoDestFile) tempVideoPath = null // This path is not used anymore // Process thumbnail - let thumbnailModel: ThumbnailModel - if (options.downloadThumbnail && options.thumbnailUrl) { - thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.MINIATURE) - } else if (options.generateThumbnail || options.downloadThumbnail) { - thumbnailModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.MINIATURE) + let thumbnailModel: MThumbnail + if (options.generateThumbnail) { + thumbnailModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.MINIATURE) } // Process preview - let previewModel: ThumbnailModel - if (options.downloadPreview && options.thumbnailUrl) { - previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.PREVIEW) - } else if (options.generatePreview || options.downloadPreview) { - previewModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.PREVIEW) + let previewModel: MThumbnail + if (options.generatePreview) { + previewModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.PREVIEW) } // Create torrent - await videoImport.Video.createTorrentAndSetInfoHash(videoFile) + await createTorrentAndSetInfoHash(videoImportWithFiles.Video, videoFile) + + const { videoImportUpdated, video } = await sequelizeTypescript.transaction(async t => { + const videoImportToUpdate = videoImportWithFiles as MVideoImportVideo - const videoImportUpdated: VideoImportModel = await sequelizeTypescript.transaction(async t => { // Refresh video - const video = await VideoModel.load(videoImport.videoId, t) - if (!video) throw new Error('Video linked to import ' + videoImport.videoId + ' does not exist anymore.') - videoImport.Video = video + const video = await VideoModel.load(videoImportToUpdate.videoId, t) + if (!video) throw new Error('Video linked to import ' + videoImportToUpdate.videoId + ' does not exist anymore.') const videoFileCreated = await videoFile.save({ transaction: t }) - video.VideoFiles = [ videoFileCreated ] + videoImportToUpdate.Video = Object.assign(video, { VideoFiles: [ videoFileCreated ] }) // Update video DB object video.duration = duration @@ -188,33 +198,28 @@ async function processFile (downloader: () => Promise, videoImport: Vide await federateVideoIfNeeded(videoForFederation, true, t) // Update video import object - videoImport.state = VideoImportState.SUCCESS - const videoImportUpdated = await videoImport.save({ transaction: t }) + videoImportToUpdate.state = VideoImportState.SUCCESS + const videoImportUpdated = await videoImportToUpdate.save({ transaction: t }) as MVideoImportVideo + videoImportUpdated.Video = video logger.info('Video %s imported.', video.uuid) - videoImportUpdated.Video = videoForFederation - return videoImportUpdated + return { videoImportUpdated, video: videoForFederation } }) Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true) - if (videoImportUpdated.Video.VideoBlacklist) { - Notifier.Instance.notifyOnVideoAutoBlacklist(videoImportUpdated.Video) + if (video.isBlacklisted()) { + const videoBlacklist = Object.assign(video.VideoBlacklist, { Video: video }) + + Notifier.Instance.notifyOnVideoAutoBlacklist(videoBlacklist) } else { - Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video) + Notifier.Instance.notifyOnNewVideoIfNeeded(video) } // Create transcoding jobs? - 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 - } - - await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) + if (video.state === VideoState.TO_TRANSCODE) { + await addOptimizeOrMergeAudioJob(videoImportUpdated.Video, videoFile) } } catch (err) { @@ -225,7 +230,9 @@ async function processFile (downloader: () => Promise, videoImport: Vide } videoImport.error = err.message - videoImport.state = VideoImportState.FAILED + if (videoImport.state !== VideoImportState.REJECTED) { + videoImport.state = VideoImportState.FAILED + } await videoImport.save() Notifier.Instance.notifyOnFinishedVideoImport(videoImport, false)