From: Kim <1877318+kimsible@users.noreply.github.com> Date: Mon, 20 Apr 2020 08:28:38 +0000 (+0200) Subject: Add thumbnail / preview generation from url on the fly (#2646) X-Git-Tag: v2.2.0-rc.1~175 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=b1770a0af464ad6350d156245b1abcc1395e142e;p=oweals%2Fpeertube.git Add thumbnail / preview generation from url on the fly (#2646) * Add thumbnails generation on the fly to URL import * Display generated preview to import first edit * Use ternary to get type inference * Move preview/thumbnail test just after import Co-authored-by: kimsible --- diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts index a17d73683..213c42333 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts +++ b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.ts @@ -11,7 +11,7 @@ import { VideoEdit } from '@app/shared/video/video-edit.model' import { FormValidatorService } from '@app/shared' import { VideoCaptionService } from '@app/shared/video-caption' import { VideoImportService } from '@app/shared/video-import' -import { scrollToTop } from '@app/shared/misc/utils' +import { scrollToTop, getAbsoluteAPIUrl } from '@app/shared/misc/utils' import { switchMap, map } from 'rxjs/operators' @Component({ @@ -95,12 +95,22 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom this.isImportingVideo = false this.hasImportedVideo = true + const absoluteAPIUrl = getAbsoluteAPIUrl() + + const thumbnailUrl = video.thumbnailPath + ? absoluteAPIUrl + video.thumbnailPath + : null + + const previewUrl = video.previewPath + ? absoluteAPIUrl + video.previewPath + : null + this.video = new VideoEdit(Object.assign(video, { commentsEnabled: videoUpdate.commentsEnabled, downloadEnabled: videoUpdate.downloadEnabled, support: null, - thumbnailUrl: null, - previewUrl: null + thumbnailUrl, + previewUrl })) this.videoCaptions = videoCaptions @@ -147,5 +157,26 @@ export class VideoImportUrlComponent extends VideoSend implements OnInit, CanCom private hydrateFormFromVideo () { this.form.patchValue(this.video.toFormPatch()) + + const objects = [ + { + url: 'thumbnailUrl', + name: 'thumbnailfile' + }, + { + url: 'previewUrl', + name: 'previewfile' + } + ] + + for (const obj of objects) { + fetch(this.video[obj.url]) + .then(response => response.blob()) + .then(data => { + this.form.patchValue({ + [ obj.name ]: data + }) + }) + } } } diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index f4630375e..fb2de5dc0 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts @@ -23,7 +23,7 @@ import { move, readFile } from 'fs-extra' import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' import { CONFIG } from '../../../initializers/config' import { sequelizeTypescript } from '../../../initializers/database' -import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' +import { createVideoMiniatureFromExisting, createVideoMiniatureFromUrl } from '../../../lib/thumbnail' import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' import { MChannelAccountDefault, @@ -153,8 +153,25 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo) - const thumbnailModel = await processThumbnail(req, video) - const previewModel = await processPreview(req, video) + let thumbnailModel: MThumbnail + + // Process video thumbnail from request.files + thumbnailModel = await processThumbnail(req, video) + + // Process video thumbnail from url if processing from request.files failed + if (!thumbnailModel) { + thumbnailModel = await processThumbnailFromUrl(youtubeDLInfo.thumbnailUrl, video) + } + + let previewModel: MThumbnail + + // Process video preview from request.files + previewModel = await processPreview(req, video) + + // Process video preview from url if processing from request.files failed + if (!previewModel) { + previewModel = await processPreviewFromUrl(youtubeDLInfo.thumbnailUrl, video) + } const tags = body.tags || youtubeDLInfo.tags const videoImportAttributes = { @@ -200,9 +217,8 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) const payload = { type: 'youtube-dl' as 'youtube-dl', videoImportId: videoImport.id, - thumbnailUrl: youtubeDLInfo.thumbnailUrl, - downloadThumbnail: !thumbnailModel, - downloadPreview: !previewModel, + generateThumbnail: !thumbnailModel, + generatePreview: !previewModel, fileExt: youtubeDLInfo.fileExt ? `.${youtubeDLInfo.fileExt}` : '.mp4' @@ -261,6 +277,24 @@ async function processPreview (req: express.Request, video: VideoModel) { return undefined } +async function processThumbnailFromUrl (url: string, video: VideoModel) { + try { + return createVideoMiniatureFromUrl(url, video, ThumbnailType.MINIATURE) + } catch (err) { + logger.warn('Cannot generate video thumbnail %s for %s.', url, video.url, { err }) + return undefined + } +} + +async function processPreviewFromUrl (url: string, video: VideoModel) { + try { + return createVideoMiniatureFromUrl(url, video, ThumbnailType.PREVIEW) + } catch (err) { + logger.warn('Cannot generate video preview %s for %s.', url, video.url, { err }) + return undefined + } +} + function insertIntoDB (parameters: { video: MVideoThumbnailAccountDefault thumbnailModel: MThumbnail diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index d8052da72..6cdae5b03 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts @@ -16,7 +16,7 @@ import { move, remove, stat } from 'fs-extra' import { Notifier } from '../../notifier' import { CONFIG } from '../../../initializers/config' import { sequelizeTypescript } from '../../../initializers/database' -import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail' +import { generateVideoMiniature } from '../../thumbnail' import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' import { MThumbnail } from '../../../typings/models/video/thumbnail' import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/typings/models/video/video-import' @@ -27,9 +27,8 @@ type VideoImportYoutubeDLPayload = { type: 'youtube-dl' videoImportId: number - thumbnailUrl: string - downloadThumbnail: boolean - downloadPreview: boolean + generateThumbnail: boolean + generatePreview: boolean fileExt?: string } @@ -64,9 +63,6 @@ async function processTorrentImport (job: Bull.Job, payload: VideoImportTorrentP const options = { videoImportId: payload.videoImportId, - downloadThumbnail: false, - downloadPreview: false, - generateThumbnail: true, generatePreview: true } @@ -84,12 +80,8 @@ async function processYoutubeDLImport (job: Bull.Job, payload: VideoImportYoutub const options = { 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, payload.fileExt, VIDEO_IMPORT_TIMEOUT), videoImport, options) @@ -107,10 +99,6 @@ async function getVideoImportOrDie (videoImportId: number) { type ProcessFileOptions = { videoImportId: number - downloadThumbnail: boolean - downloadPreview: boolean - thumbnailUrl?: string - generateThumbnail: boolean generatePreview: boolean } @@ -155,29 +143,13 @@ async function processFile (downloader: () => Promise, videoImport: MVid // Process thumbnail let thumbnailModel: MThumbnail - if (options.downloadThumbnail && options.thumbnailUrl) { - try { - thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.MINIATURE) - } catch (err) { - logger.warn('Cannot generate video thumbnail %s for %s.', options.thumbnailUrl, videoImportWithFiles.Video.url, { err }) - } - } - - if (!thumbnailModel && (options.generateThumbnail || options.downloadThumbnail)) { + if (options.generateThumbnail) { thumbnailModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.MINIATURE) } // Process preview let previewModel: MThumbnail - if (options.downloadPreview && options.thumbnailUrl) { - try { - previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.PREVIEW) - } catch (err) { - logger.warn('Cannot generate video preview %s for %s.', options.thumbnailUrl, videoImportWithFiles.Video.url, { err }) - } - } - - if (!previewModel && (options.generatePreview || options.downloadPreview)) { + if (options.generatePreview) { previewModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.PREVIEW) } diff --git a/server/tests/api/videos/video-imports.ts b/server/tests/api/videos/video-imports.ts index 8e179b825..4d5989f43 100644 --- a/server/tests/api/videos/video-imports.ts +++ b/server/tests/api/videos/video-imports.ts @@ -19,6 +19,7 @@ import { } from '../../../../shared/extra-utils' import { waitJobs } from '../../../../shared/extra-utils/server/jobs' import { getMagnetURI, getMyVideoImports, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports' +import { testImage } from '../../../../shared/extra-utils/miscs/miscs' const expect = chai.expect @@ -118,6 +119,10 @@ describe('Test video imports', function () { const attributes = immutableAssign(baseAttributes, { targetUrl: getYoutubeVideoUrl() }) const res = await importVideo(servers[0].url, servers[0].accessToken, attributes) expect(res.body.video.name).to.equal('small video - youtube') + expect(res.body.video.thumbnailPath).to.equal(`/static/thumbnails/${res.body.video.uuid}.jpg`) + expect(res.body.video.previewPath).to.equal(`/static/previews/${res.body.video.uuid}.jpg`) + await testImage(servers[0].url, 'video_import_thumbnail', res.body.video.thumbnailPath) + await testImage(servers[0].url, 'video_import_preview', res.body.video.previewPath) const resCaptions = await listVideoCaptions(servers[0].url, res.body.video.id) const videoCaptions: VideoCaption[] = resCaptions.body.data diff --git a/server/tests/fixtures/video_import_preview.jpg b/server/tests/fixtures/video_import_preview.jpg new file mode 100644 index 000000000..1f8d1d91d Binary files /dev/null and b/server/tests/fixtures/video_import_preview.jpg differ diff --git a/server/tests/fixtures/video_import_thumbnail.jpg b/server/tests/fixtures/video_import_thumbnail.jpg new file mode 100644 index 000000000..fcc50b75f Binary files /dev/null and b/server/tests/fixtures/video_import_thumbnail.jpg differ