From b5b687550d8ef8beafdf706e45d6556fb5f4c876 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 26 Oct 2020 16:44:23 +0100 Subject: Add ability to save live replay --- server/lib/job-queue/handlers/video-live-ending.ts | 84 ++++++++++++++++++---- server/lib/job-queue/handlers/video-transcoding.ts | 30 +++++--- 2 files changed, 94 insertions(+), 20 deletions(-) (limited to 'server/lib/job-queue') diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts index 1a58a9f7e..1a9a36129 100644 --- a/server/lib/job-queue/handlers/video-live-ending.ts +++ b/server/lib/job-queue/handlers/video-live-ending.ts @@ -1,24 +1,89 @@ import * as Bull from 'bull' import { readdir, remove } from 'fs-extra' import { join } from 'path' +import { getVideoFileResolution, hlsPlaylistToFragmentedMP4 } from '@server/helpers/ffmpeg-utils' import { getHLSDirectory } from '@server/lib/video-paths' +import { generateHlsPlaylist } from '@server/lib/video-transcoding' import { VideoModel } from '@server/models/video/video' +import { VideoLiveModel } from '@server/models/video/video-live' import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' -import { VideoLiveEndingPayload } from '@shared/models' +import { MStreamingPlaylist, MVideo } from '@server/types/models' +import { VideoLiveEndingPayload, VideoState } from '@shared/models' import { logger } from '../../../helpers/logger' async function processVideoLiveEnding (job: Bull.Job) { const payload = job.data as VideoLiveEndingPayload - const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId) - if (!video) { - logger.warn('Video live %d does not exist anymore. Cannot cleanup.', payload.videoId) + const video = await VideoModel.load(payload.videoId) + const live = await VideoLiveModel.loadByVideoId(payload.videoId) + + const streamingPlaylist = await VideoStreamingPlaylistModel.loadHLSPlaylistByVideo(video.id) + if (!video || !streamingPlaylist || !live) { + logger.warn('Video live %d does not exist anymore. Cannot process live ending.', payload.videoId) return } - const streamingPlaylist = await VideoStreamingPlaylistModel.loadHLSPlaylistByVideo(video.id) + if (live.saveReplay !== true) { + return cleanupLive(video, streamingPlaylist) + } + + return saveLive(video, streamingPlaylist) +} + +// --------------------------------------------------------------------------- + +export { + processVideoLiveEnding +} + +// --------------------------------------------------------------------------- + +async function saveLive (video: MVideo, streamingPlaylist: MStreamingPlaylist) { + const videoFiles = await streamingPlaylist.get('VideoFiles') + const hlsDirectory = getHLSDirectory(video, false) + + for (const videoFile of videoFiles) { + const playlistPath = join(hlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(videoFile.resolution)) + + const mp4TmpName = buildMP4TmpName(videoFile.resolution) + await hlsPlaylistToFragmentedMP4(playlistPath, mp4TmpName) + } + + await cleanupLiveFiles(hlsDirectory) + + video.isLive = false + video.state = VideoState.TO_TRANSCODE + await video.save() + + const videoWithFiles = await VideoModel.loadWithFiles(video.id) + + for (const videoFile of videoFiles) { + const videoInputPath = buildMP4TmpName(videoFile.resolution) + const { isPortraitMode } = await getVideoFileResolution(videoInputPath) + + await generateHlsPlaylist({ + video: videoWithFiles, + videoInputPath, + resolution: videoFile.resolution, + copyCodecs: true, + isPortraitMode + }) + } + + video.state = VideoState.PUBLISHED + await video.save() +} + +async function cleanupLive (video: MVideo, streamingPlaylist: MStreamingPlaylist) { const hlsDirectory = getHLSDirectory(video, false) + await cleanupLiveFiles(hlsDirectory) + + streamingPlaylist.destroy() + .catch(err => logger.error('Cannot remove live streaming playlist.', { err })) +} + +async function cleanupLiveFiles (hlsDirectory: string) { const files = await readdir(hlsDirectory) for (const filename of files) { @@ -35,13 +100,8 @@ async function processVideoLiveEnding (job: Bull.Job) { .catch(err => logger.error('Cannot remove %s.', p, { err })) } } - - streamingPlaylist.destroy() - .catch(err => logger.error('Cannot remove live streaming playlist.', { err })) } -// --------------------------------------------------------------------------- - -export { - processVideoLiveEnding +function buildMP4TmpName (resolution: number) { + return resolution + 'tmp.mp4' } diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 6659ab716..2aebc29f7 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts @@ -1,21 +1,22 @@ import * as Bull from 'bull' +import { getVideoFilePath } from '@server/lib/video-paths' +import { MVideoFullLight, MVideoUUID, MVideoWithFile } from '@server/types/models' import { MergeAudioTranscodingPayload, NewResolutionTranscodingPayload, OptimizeTranscodingPayload, VideoTranscodingPayload } from '../../../../shared' +import { retryTransactionWrapper } from '../../../helpers/database-utils' +import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' import { logger } from '../../../helpers/logger' +import { CONFIG } from '../../../initializers/config' +import { sequelizeTypescript } from '../../../initializers/database' import { VideoModel } from '../../../models/video/video' -import { JobQueue } from '../job-queue' import { federateVideoIfNeeded } from '../../activitypub/videos' -import { retryTransactionWrapper } from '../../../helpers/database-utils' -import { sequelizeTypescript } from '../../../initializers/database' -import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' -import { generateHlsPlaylist, mergeAudioVideofile, optimizeOriginalVideofile, transcodeNewResolution } from '../../video-transcoding' import { Notifier } from '../../notifier' -import { CONFIG } from '../../../initializers/config' -import { MVideoFullLight, MVideoUUID, MVideoWithFile } from '@server/types/models' +import { generateHlsPlaylist, mergeAudioVideofile, optimizeOriginalVideofile, transcodeNewResolution } from '../../video-transcoding' +import { JobQueue } from '../job-queue' async function processVideoTranscoding (job: Bull.Job) { const payload = job.data as VideoTranscodingPayload @@ -29,7 +30,20 @@ async function processVideoTranscoding (job: Bull.Job) { } if (payload.type === 'hls') { - await generateHlsPlaylist(video, payload.resolution, payload.copyCodecs, payload.isPortraitMode || false) + const videoFileInput = payload.copyCodecs + ? video.getWebTorrentFile(payload.resolution) + : video.getMaxQualityFile() + + const videoOrStreamingPlaylist = videoFileInput.getVideoOrStreamingPlaylist() + const videoInputPath = getVideoFilePath(videoOrStreamingPlaylist, videoFileInput) + + await generateHlsPlaylist({ + video, + videoInputPath, + resolution: payload.resolution, + copyCodecs: payload.copyCodecs, + isPortraitMode: payload.isPortraitMode || false + }) await retryTransactionWrapper(onHlsPlaylistGenerationSuccess, video) } else if (payload.type === 'new-resolution') { -- cgit v1.2.3