X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Flib%2Fjob-queue%2Fhandlers%2Fvideo-live-ending.ts;h=386ccdc7b6d76e702140c63d25b4992ba1a70aa5;hb=764b1a14fc494f2cfd7ea590d2f07b01df65c7ad;hp=3d93417388bc6cda6382768a1fa15f77fdb85b22;hpb=053aed43fb255b4ae4324a845534f2f562c3b6cc;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 3d9341738..386ccdc7b 100644 --- a/server/lib/job-queue/handlers/video-live-ending.ts +++ b/server/lib/job-queue/handlers/video-live-ending.ts @@ -1,10 +1,13 @@ import * as Bull from 'bull' -import { readdir, remove } from 'fs-extra' +import { pathExists, readdir, remove } from 'fs-extra' import { join } from 'path' -import { getDurationFromVideoFile, getVideoFileResolution, hlsPlaylistToFragmentedMP4 } from '@server/helpers/ffmpeg-utils' +import { ffprobePromise, getAudioStream, getDurationFromVideoFile, getVideoFileResolution } from '@server/helpers/ffprobe-utils' +import { VIDEO_LIVE } from '@server/initializers/constants' +import { buildConcatenatedName, cleanupLive, LiveSegmentShaStore } from '@server/lib/live' +import { generateVideoMiniature } from '@server/lib/thumbnail' +import { generateHlsPlaylistResolutionFromTS } from '@server/lib/transcoding/video-transcoding' import { publishAndFederateIfNeeded } from '@server/lib/video' -import { getHLSDirectory } from '@server/lib/video-paths' -import { generateHlsPlaylist } from '@server/lib/video-transcoding' +import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getHLSDirectory } from '@server/lib/video-paths' import { VideoModel } from '@server/models/video/video' import { VideoFileModel } from '@server/models/video/video-file' import { VideoLiveModel } from '@server/models/video/video-live' @@ -12,7 +15,6 @@ import { VideoStreamingPlaylistModel } from '@server/models/video/video-streamin 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 @@ -35,11 +37,13 @@ async function processVideoLiveEnding (job: Bull.Job) { return } + LiveSegmentShaStore.Instance.cleanupShaSegments(video.uuid) + if (live.saveReplay !== true) { return cleanupLive(video, streamingPlaylist) } - return saveLive(video, live) + return saveLive(video, live, streamingPlaylist) } // --------------------------------------------------------------------------- @@ -50,88 +54,91 @@ export { // --------------------------------------------------------------------------- -async function saveLive (video: MVideo, live: MVideoLive) { +async function saveLive (video: MVideo, live: MVideoLive, streamingPlaylist: MStreamingPlaylist) { 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) + const replayDirectory = join(hlsDirectory, VIDEO_LIVE.REPLAY_DIRECTORY) - // Playlist name is for example 3.m3u8 - // Segments names are 3-0.ts 3-1.ts etc - const shouldStartWith = playlistFile.replace(/\.m3u8$/, '') + '-' + const rootFiles = await readdir(hlsDirectory) - 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) - } + const playlistFiles = rootFiles.filter(file => { + return file.endsWith('.m3u8') && file !== streamingPlaylist.playlistFilename + }) await cleanupLiveFiles(hlsDirectory) await live.destroy() video.isLive = false + // Reinit views + video.views = 0 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 videoWithFiles = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.id) const hlsPlaylist = videoWithFiles.getHLSPlaylist() await VideoFileModel.removeHLSFilesOfVideoId(hlsPlaylist.id) + + // Reset playlist hlsPlaylist.VideoFiles = [] + hlsPlaylist.playlistFilename = generateHLSMasterPlaylistFilename() + hlsPlaylist.segmentsSha256Filename = generateHlsSha256SegmentsFilename() + await hlsPlaylist.save() + + let durationDone = false + + for (const playlistFile of playlistFiles) { + const concatenatedTsFile = buildConcatenatedName(playlistFile) + const concatenatedTsFilePath = join(replayDirectory, concatenatedTsFile) - for (const resolution of resolutions) { - const videoInputPath = buildMP4TmpPath(hlsDirectory, resolution) - const { isPortraitMode } = await getVideoFileResolution(videoInputPath) + const probe = await ffprobePromise(concatenatedTsFilePath) + const { audioStream } = await getAudioStream(concatenatedTsFilePath, probe) - await generateHlsPlaylist({ + const { videoFileResolution, isPortraitMode } = await getVideoFileResolution(concatenatedTsFilePath, probe) + + const outputPath = await generateHlsPlaylistResolutionFromTS({ video: videoWithFiles, - videoInputPath, - resolution: resolution, - copyCodecs: true, - isPortraitMode + concatenatedTsFilePath, + resolution: videoFileResolution, + isPortraitMode, + isAAC: audioStream?.codec_name === 'aac' }) - await remove(videoInputPath) + if (!durationDone) { + videoWithFiles.duration = await getDurationFromVideoFile(outputPath) + await videoWithFiles.save() + + durationDone = true + } } + await remove(replayDirectory) + // Regenerate the thumbnail & preview? if (videoWithFiles.getMiniature().automaticallyGenerated === true) { - await generateVideoMiniature(videoWithFiles, videoWithFiles.getMaxQualityFile(), ThumbnailType.MINIATURE) + await generateVideoMiniature({ + video: videoWithFiles, + videoFile: videoWithFiles.getMaxQualityFile(), + type: ThumbnailType.MINIATURE + }) } if (videoWithFiles.getPreview().automaticallyGenerated === true) { - await generateVideoMiniature(videoWithFiles, videoWithFiles.getMaxQualityFile(), ThumbnailType.PREVIEW) + await generateVideoMiniature({ + video: videoWithFiles, + videoFile: videoWithFiles.getMaxQualityFile(), + type: ThumbnailType.PREVIEW + }) } - await publishAndFederateIfNeeded(video, true) -} - -async function cleanupLive (video: MVideo, streamingPlaylist: MStreamingPlaylist) { - const hlsDirectory = getHLSDirectory(video, false) - - await remove(hlsDirectory) - - streamingPlaylist.destroy() - .catch(err => logger.error('Cannot remove live streaming playlist.', { err })) + await publishAndFederateIfNeeded(videoWithFiles, true) } async function cleanupLiveFiles (hlsDirectory: string) { + if (!await pathExists(hlsDirectory)) return + const files = await readdir(hlsDirectory) for (const filename of files) { @@ -149,7 +156,3 @@ async function cleanupLiveFiles (hlsDirectory: string) { } } } - -function buildMP4TmpPath (basePath: string, resolution: number) { - return join(basePath, resolution + '-tmp.mp4') -}