X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Flib%2Fjob-queue%2Fhandlers%2Fvideo-live-ending.ts;h=55c7a4ccbc84dc74bdf331e1e702bba8ef213ac6;hb=529f037294d9917a62235f8162887a8edc04c32f;hp=1a58a9f7ee2c34d38ca91528ade36d252dd632e8;hpb=a5cf76afa378aae81af2a9b0ce548e5d2582f832;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/lib/job-queue/handlers/video-live-ending.ts b/server/lib/job-queue/handlers/video-live-ending.ts index 1a58a9f7e..55c7a4ccb 100644 --- a/server/lib/job-queue/handlers/video-live-ending.ts +++ b/server/lib/job-queue/handlers/video-live-ending.ts @@ -1,24 +1,137 @@ import * as Bull from 'bull' import { readdir, remove } from 'fs-extra' import { join } from 'path' +import { getDurationFromVideoFile, getVideoFileResolution, hlsPlaylistToFragmentedMP4 } from '@server/helpers/ffmpeg-utils' +import { publishAndFederateIfNeeded } from '@server/lib/video' import { getHLSDirectory } from '@server/lib/video-paths' +import { generateHlsPlaylist } from '@server/lib/video-transcoding' import { VideoModel } from '@server/models/video/video' +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 { VideoLiveEndingPayload } from '@shared/models' +import { MStreamingPlaylist, MVideo, MVideoLive } from '@server/types/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 - const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId) - if (!video) { - logger.warn('Video live %d does not exist anymore. Cannot cleanup.', payload.videoId) + function logError () { + logger.warn('Video live %d does not exist anymore. Cannot process live ending.', payload.videoId) + } + + const video = await VideoModel.load(payload.videoId) + const live = await VideoLiveModel.loadByVideoId(payload.videoId) + + if (!video || !live) { + logError() return } const streamingPlaylist = await VideoStreamingPlaylistModel.loadHLSPlaylistByVideo(video.id) + if (!streamingPlaylist) { + logError() + return + } + + if (live.saveReplay !== true) { + return cleanupLive(video, streamingPlaylist) + } + + return saveLive(video, live) +} + +// --------------------------------------------------------------------------- + +export { + processVideoLiveEnding +} + +// --------------------------------------------------------------------------- + +async function saveLive (video: MVideo, live: MVideoLive) { const hlsDirectory = getHLSDirectory(video, false) + const files = await readdir(hlsDirectory) + + const playlistFiles = files.filter(f => f.endsWith('.m3u8') && f !== 'master.m3u8') + const resolutions: number[] = [] + let duration: number + + for (const playlistFile of playlistFiles) { + const playlistPath = join(hlsDirectory, playlistFile) + const { videoFileResolution } = await getVideoFileResolution(playlistPath) + + const mp4TmpPath = buildMP4TmpPath(hlsDirectory, videoFileResolution) + + // Playlist name is for example 3.m3u8 + // Segments names are 3-0.ts 3-1.ts etc + const shouldStartWith = playlistFile.replace(/\.m3u8$/, '') + '-' + + const segmentFiles = files.filter(f => f.startsWith(shouldStartWith) && f.endsWith('.ts')) + await hlsPlaylistToFragmentedMP4(hlsDirectory, segmentFiles, mp4TmpPath) + + if (!duration) { + duration = await getDurationFromVideoFile(mp4TmpPath) + } + + resolutions.push(videoFileResolution) + } + + await cleanupLiveFiles(hlsDirectory) + await live.destroy() + + video.isLive = false + video.state = VideoState.TO_TRANSCODE + video.duration = duration + + await video.save() + + // Remove old HLS playlist video files + const videoWithFiles = await VideoModel.loadWithFiles(video.id) + + const hlsPlaylist = videoWithFiles.getHLSPlaylist() + await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id) + hlsPlaylist.VideoFiles = [] + + for (const resolution of resolutions) { + const videoInputPath = buildMP4TmpPath(hlsDirectory, resolution) + const { isPortraitMode } = await getVideoFileResolution(videoInputPath) + + await generateHlsPlaylist({ + video: videoWithFiles, + videoInputPath, + resolution: resolution, + copyCodecs: true, + isPortraitMode + }) + + 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) +} + +async function cleanupLive (video: MVideo, streamingPlaylist: MStreamingPlaylist) { + const hlsDirectory = getHLSDirectory(video) + + await remove(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 +148,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 buildMP4TmpPath (basePath: string, resolution: number) { + return join(basePath, resolution + '-tmp.mp4') }