From 053aed43fb255b4ae4324a845534f2f562c3b6cc Mon Sep 17 00:00:00 2001
From: Chocobozzz <me@florianbigard.com>
Date: Fri, 6 Nov 2020 10:57:40 +0100
Subject: [PATCH] Regenerate miniature on live save

---
 .../assets/player/peertube-player-manager.ts  |  6 +--
 server/controllers/activitypub/client.ts      | 40 +++++++++----------
 server/controllers/api/videos/index.ts        |  2 +-
 server/helpers/ffmpeg-utils.ts                |  2 +-
 server/lib/activitypub/videos.ts              |  7 +---
 .../job-queue/handlers/video-live-ending.ts   | 12 +++++-
 server/lib/live-manager.ts                    |  8 +++-
 server/lib/video-paths.ts                     |  4 +-
 server/lib/video-transcoding.ts               | 19 +++++----
 server/lib/video.ts                           |  2 +-
 server/models/video/video-file.ts             |  4 ++
 server/tests/api/live/live.ts                 | 24 +----------
 12 files changed, 61 insertions(+), 69 deletions(-)

diff --git a/client/src/assets/player/peertube-player-manager.ts b/client/src/assets/player/peertube-player-manager.ts
index 3d72d4609..c1953043e 100644
--- a/client/src/assets/player/peertube-player-manager.ts
+++ b/client/src/assets/player/peertube-player-manager.ts
@@ -339,10 +339,8 @@ export class PeertubePlayerManager {
         const resolution = Math.min(level.height || 0, level.width || 0)
 
         const file = p2pMediaLoaderOptions.videoFiles.find(f => f.resolution.id === resolution)
-        if (!file) {
-          console.error('Cannot find video file for level %d.', level.height)
-          return level.height
-        }
+        // We don't have files for live videos
+        if (!file) return level.height
 
         let label = file.resolution.label
         if (file.fps >= 50) label += file.fps
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index df2a01d2c..d85d0aa5f 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -1,11 +1,22 @@
-import * as express from 'express'
 import * as cors from 'cors'
+import * as express from 'express'
+import { getRateUrl } from '@server/lib/activitypub/video-rates'
+import { getServerActor } from '@server/models/application/application'
+import { MAccountId, MActorId, MChannelId, MVideoId } from '@server/types/models'
 import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos'
+import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
 import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub'
 import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants'
-import { buildAnnounceWithVideoAudience, buildLikeActivity } from '../../lib/activitypub/send'
 import { audiencify, getAudience } from '../../lib/activitypub/audience'
+import { buildAnnounceWithVideoAudience, buildLikeActivity } from '../../lib/activitypub/send'
 import { buildCreateActivity } from '../../lib/activitypub/send/send-create'
+import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike'
+import {
+  getVideoCommentsActivityPubUrl,
+  getVideoDislikesActivityPubUrl,
+  getVideoLikesActivityPubUrl,
+  getVideoSharesActivityPubUrl
+} from '../../lib/activitypub/url'
 import {
   asyncMiddleware,
   executeIfActivityPub,
@@ -14,30 +25,19 @@ import {
   videosCustomGetValidator,
   videosShareValidator
 } from '../../middlewares'
+import { cacheRoute } from '../../middlewares/cache'
 import { getAccountVideoRateValidatorFactory, videoCommentGetValidator } from '../../middlewares/validators'
+import { videoFileRedundancyGetValidator, videoPlaylistRedundancyGetValidator } from '../../middlewares/validators/redundancy'
+import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists'
 import { AccountModel } from '../../models/account/account'
+import { AccountVideoRateModel } from '../../models/account/account-video-rate'
 import { ActorFollowModel } from '../../models/activitypub/actor-follow'
 import { VideoModel } from '../../models/video/video'
-import { VideoCommentModel } from '../../models/video/video-comment'
-import { VideoShareModel } from '../../models/video/video-share'
-import { cacheRoute } from '../../middlewares/cache'
-import { activityPubResponse } from './utils'
-import { AccountVideoRateModel } from '../../models/account/account-video-rate'
-import {
-  getVideoCommentsActivityPubUrl,
-  getVideoDislikesActivityPubUrl,
-  getVideoLikesActivityPubUrl,
-  getVideoSharesActivityPubUrl
-} from '../../lib/activitypub/url'
 import { VideoCaptionModel } from '../../models/video/video-caption'
-import { videoFileRedundancyGetValidator, videoPlaylistRedundancyGetValidator } from '../../middlewares/validators/redundancy'
-import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike'
-import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists'
+import { VideoCommentModel } from '../../models/video/video-comment'
 import { VideoPlaylistModel } from '../../models/video/video-playlist'
-import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
-import { MAccountId, MActorId, MVideoAPWithoutCaption, MVideoId, MChannelId } from '@server/types/models'
-import { getServerActor } from '@server/models/application/application'
-import { getRateUrl } from '@server/lib/activitypub/video-rates'
+import { VideoShareModel } from '../../models/video/video-share'
+import { activityPubResponse } from './utils'
 
 const activityPubClientRouter = express.Router()
 activityPubClientRouter.use(cors())
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 50e769e77..ff29e584b 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -9,7 +9,7 @@ import { getVideoActivityPubUrl } from '@server/lib/activitypub/url'
 import { buildLocalVideoFromReq, buildVideoThumbnailsFromReq, setVideoTags } from '@server/lib/video'
 import { getVideoFilePath } from '@server/lib/video-paths'
 import { getServerActor } from '@server/models/application/application'
-import { MVideoDetails, MVideoFullLight } from '@server/types/models'
+import { MVideoFullLight } from '@server/types/models'
 import { VideoCreate, VideoState, VideoUpdate } from '../../../../shared'
 import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
 import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts
index 7a54b4642..b6f4097aa 100644
--- a/server/helpers/ffmpeg-utils.ts
+++ b/server/helpers/ffmpeg-utils.ts
@@ -1,5 +1,5 @@
 import * as ffmpeg from 'fluent-ffmpeg'
-import { outputFile, readFile, remove, writeFile } from 'fs-extra'
+import { readFile, remove, writeFile } from 'fs-extra'
 import { dirname, join } from 'path'
 import { VideoFileMetadata } from '@shared/models/videos/video-file-metadata'
 import { getMaxBitrate, getTargetBitrate, VideoResolution } from '../../shared/models/videos'
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts
index ab4aac0a1..4053f487c 100644
--- a/server/lib/activitypub/videos.ts
+++ b/server/lib/activitypub/videos.ts
@@ -1,10 +1,10 @@
-import { VideoLiveModel } from '@server/models/video/video-live'
 import * as Bluebird from 'bluebird'
 import { maxBy, minBy } from 'lodash'
 import * as magnetUtil from 'magnet-uri'
 import { join } from 'path'
 import * as request from 'request'
 import * as sequelize from 'sequelize'
+import { VideoLiveModel } from '@server/models/video/video-live'
 import {
   ActivityHashTagObject,
   ActivityMagnetUrlObject,
@@ -13,8 +13,7 @@ import {
   ActivitypubHttpFetcherPayload,
   ActivityTagObject,
   ActivityUrlObject,
-  ActivityVideoUrlObject,
-  VideoState
+  ActivityVideoUrlObject
 } from '../../../shared/index'
 import { VideoObject } from '../../../shared/models/activitypub/objects'
 import { VideoPrivacy } from '../../../shared/models/videos'
@@ -562,8 +561,6 @@ function isAPHashTagObject (url: any): url is ActivityHashTagObject {
   return url && url.type === 'Hashtag'
 }
 
-
-
 async function createVideo (videoObject: VideoObject, channel: MChannelAccountLight, waitThumbnail = false) {
   logger.debug('Adding remote video %s.', videoObject.id)
 
diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts
index 3892260c4..3d9341738 100644
--- a/server/lib/job-queue/handlers/video-live-ending.ts
+++ b/server/lib/job-queue/handlers/video-live-ending.ts
@@ -10,8 +10,9 @@ import { VideoFileModel } from '@server/models/video/video-file'
 import { VideoLiveModel } from '@server/models/video/video-live'
 import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist'
 import { MStreamingPlaylist, MVideo, MVideoLive } from '@server/types/models'
-import { VideoLiveEndingPayload, VideoState } from '@shared/models'
+import { ThumbnailType, VideoLiveEndingPayload, VideoState } from '@shared/models'
 import { logger } from '../../../helpers/logger'
+import { generateVideoMiniature } from '@server/lib/thumbnail'
 
 async function processVideoLiveEnding (job: Bull.Job) {
   const payload = job.data as VideoLiveEndingPayload
@@ -109,6 +110,15 @@ async function saveLive (video: MVideo, live: MVideoLive) {
     await remove(videoInputPath)
   }
 
+  // Regenerate the thumbnail & preview?
+  if (videoWithFiles.getMiniature().automaticallyGenerated === true) {
+    await generateVideoMiniature(videoWithFiles, videoWithFiles.getMaxQualityFile(), ThumbnailType.MINIATURE)
+  }
+
+  if (videoWithFiles.getPreview().automaticallyGenerated === true) {
+    await generateVideoMiniature(videoWithFiles, videoWithFiles.getMaxQualityFile(), ThumbnailType.PREVIEW)
+  }
+
   await publishAndFederateIfNeeded(video, true)
 }
 
diff --git a/server/lib/live-manager.ts b/server/lib/live-manager.ts
index d253d06fc..9a2914cc5 100644
--- a/server/lib/live-manager.ts
+++ b/server/lib/live-manager.ts
@@ -4,7 +4,13 @@ import * as chokidar from 'chokidar'
 import { FfmpegCommand } from 'fluent-ffmpeg'
 import { ensureDir, stat } from 'fs-extra'
 import { basename } from 'path'
-import { computeResolutionsToTranscode, getVideoFileFPS, getVideoFileResolution, getVideoStreamCodec, getVideoStreamSize, runLiveMuxing, runLiveTranscoding } from '@server/helpers/ffmpeg-utils'
+import {
+  computeResolutionsToTranscode,
+  getVideoFileFPS,
+  getVideoFileResolution,
+  runLiveMuxing,
+  runLiveTranscoding
+} from '@server/helpers/ffmpeg-utils'
 import { logger } from '@server/helpers/logger'
 import { CONFIG, registerConfigChangedHandler } from '@server/initializers/config'
 import { MEMOIZE_TTL, P2P_MEDIA_LOADER_PEER_VERSION, VIDEO_LIVE, WEBSERVER } from '@server/initializers/constants'
diff --git a/server/lib/video-paths.ts b/server/lib/video-paths.ts
index b6cb39d25..53fc8e81d 100644
--- a/server/lib/video-paths.ts
+++ b/server/lib/video-paths.ts
@@ -9,7 +9,7 @@ import { extractVideo } from '@server/helpers/video'
 function getVideoFilename (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile) {
   const video = extractVideo(videoOrPlaylist)
 
-  if (isStreamingPlaylist(videoOrPlaylist)) {
+  if (videoFile.isHLS()) {
     return generateVideoStreamingPlaylistName(video.uuid, videoFile.resolution)
   }
 
@@ -25,7 +25,7 @@ function generateWebTorrentVideoName (uuid: string, resolution: number, extname:
 }
 
 function getVideoFilePath (videoOrPlaylist: MVideo | MStreamingPlaylistVideo, videoFile: MVideoFile, isRedundancy = false) {
-  if (isStreamingPlaylist(videoOrPlaylist)) {
+  if (videoFile.isHLS()) {
     const video = extractVideo(videoOrPlaylist)
 
     return join(getHLSDirectory(video), getVideoFilename(videoOrPlaylist, videoFile))
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts
index c62b3c1ce..e267b1397 100644
--- a/server/lib/video-transcoding.ts
+++ b/server/lib/video-transcoding.ts
@@ -1,5 +1,9 @@
-import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants'
+import { copyFile, ensureDir, move, remove, stat } from 'fs-extra'
 import { basename, extname as extnameUtil, join } from 'path'
+import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
+import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoWithAllFiles, MVideoWithFile } from '@server/types/models'
+import { VideoResolution } from '../../shared/models/videos'
+import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
 import {
   canDoQuickTranscode,
   getDurationFromVideoFile,
@@ -9,18 +13,13 @@ import {
   TranscodeOptions,
   TranscodeOptionsType
 } from '../helpers/ffmpeg-utils'
-import { copyFile, ensureDir, move, remove, stat } from 'fs-extra'
 import { logger } from '../helpers/logger'
-import { VideoResolution } from '../../shared/models/videos'
-import { VideoFileModel } from '../models/video/video-file'
-import { updateMasterHLSPlaylist, updateSha256VODSegments } from './hls'
-import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
-import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type'
 import { CONFIG } from '../initializers/config'
-import { MStreamingPlaylistFilesVideo, MVideoFile, MVideoWithAllFiles, MVideoWithFile } from '@server/types/models'
-import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
+import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants'
+import { VideoFileModel } from '../models/video/video-file'
+import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
+import { updateMasterHLSPlaylist, updateSha256VODSegments } from './hls'
 import { generateVideoStreamingPlaylistName, getVideoFilename, getVideoFilePath } from './video-paths'
-import { spawn } from 'child_process'
 
 /**
  * Optimize the original video file and replace it. The resolution is not changed.
diff --git a/server/lib/video.ts b/server/lib/video.ts
index 8d9918b2d..d03ab0452 100644
--- a/server/lib/video.ts
+++ b/server/lib/video.ts
@@ -4,7 +4,7 @@ import { TagModel } from '@server/models/video/tag'
 import { VideoModel } from '@server/models/video/video'
 import { FilteredModelAttributes } from '@server/types'
 import { MTag, MThumbnail, MVideoTag, MVideoThumbnail, MVideoUUID } from '@server/types/models'
-import { ThumbnailType, VideoCreate, VideoPrivacy, VideoState } from '@shared/models'
+import { ThumbnailType, VideoCreate, VideoPrivacy } from '@shared/models'
 import { federateVideoIfNeeded } from './activitypub/videos'
 import { Notifier } from './notifier'
 import { createVideoMiniatureFromExisting } from './thumbnail'
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts
index 5048cf9b7..0e834aee0 100644
--- a/server/models/video/video-file.ts
+++ b/server/models/video/video-file.ts
@@ -333,6 +333,10 @@ export class VideoFileModel extends Model<VideoFileModel> {
     return this.size === -1
   }
 
+  isHLS () {
+    return this.videoStreamingPlaylistId !== null
+  }
+
   hasSameUniqueKeysThan (other: MVideoFile) {
     return this.fps === other.fps &&
       this.resolution === other.resolution &&
diff --git a/server/tests/api/live/live.ts b/server/tests/api/live/live.ts
index c795f201a..b41b5fc2e 100644
--- a/server/tests/api/live/live.ts
+++ b/server/tests/api/live/live.ts
@@ -3,18 +3,16 @@
 import 'mocha'
 import * as chai from 'chai'
 import { getLiveNotificationSocket } from '@shared/extra-utils/socket/socket-io'
-import { LiveVideo, LiveVideoCreate, User, Video, VideoDetails, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models'
+import { LiveVideo, LiveVideoCreate, Video, VideoDetails, VideoPrivacy, VideoState, VideoStreamingPlaylistType } from '@shared/models'
 import {
   addVideoToBlacklist,
   checkLiveCleanup,
   checkResolutionsInMasterPlaylist,
   cleanupTests,
   createLive,
-  createUser,
   doubleFollow,
   flushAndRunMultipleServers,
   getLive,
-  getMyUserInformation,
   getVideo,
   getVideoIdFromUUID,
   getVideosList,
@@ -30,7 +28,6 @@ import {
   testImage,
   updateCustomSubConfig,
   updateLive,
-  userLogin,
   waitJobs,
   waitUntilLiveStarts
 } from '../../../../shared/extra-utils'
@@ -39,9 +36,6 @@ const expect = chai.expect
 
 describe('Test live', function () {
   let servers: ServerInfo[] = []
-  let userId: number
-  let userAccessToken: string
-  let userChannelId: number
 
   before(async function () {
     this.timeout(120000)
@@ -62,22 +56,6 @@ describe('Test live', function () {
       }
     })
 
-    {
-      const user = { username: 'user1', password: 'superpassword' }
-      const res = await createUser({
-        url: servers[0].url,
-        accessToken: servers[0].accessToken,
-        username: user.username,
-        password: user.password
-      })
-      userId = res.body.user.id
-
-      userAccessToken = await userLogin(servers[0], user)
-
-      const resMe = await getMyUserInformation(servers[0].url, userAccessToken)
-      userChannelId = (resMe.body as User).videoChannels[0].id
-    }
-
     // Server 1 and server 2 follow each other
     await doubleFollow(servers[0], servers[1])
   })