Fix live replay privacy change

pull/5992/head
Chocobozzz 2023-09-01 16:47:25 +02:00
parent a1d9318066
commit 1022e27309
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
5 changed files with 144 additions and 20 deletions

View File

@ -102,7 +102,9 @@ export class FFmpegVOD {
command.on('start', () => {
setTimeout(() => {
options.inputFileMutexReleaser()
if (options.inputFileMutexReleaser) {
options.inputFileMutexReleaser()
}
}, 1000)
})

View File

@ -1,6 +1,7 @@
import './live-constraints.js'
import './live-fast-restream.js'
import './live-socket-messages.js'
import './live-privacy-update.js'
import './live-permanent.js'
import './live-rtmps.js'
import './live-save-replay.js'

View File

@ -0,0 +1,83 @@
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
import { HttpStatusCode, LiveVideoCreate, VideoPrivacy } from '@peertube/peertube-models'
import {
cleanupTests, createSingleServer, makeRawRequest,
PeerTubeServer,
setAccessTokensToServers,
setDefaultVideoChannel,
stopFfmpeg,
waitJobs,
waitUntilLivePublishedOnAllServers,
waitUntilLiveReplacedByReplayOnAllServers
} from '@peertube/peertube-server-commands'
async function testVideoFiles (server: PeerTubeServer, uuid: string) {
const video = await server.videos.getWithToken({ id: uuid })
const expectedStatus = HttpStatusCode.OK_200
await makeRawRequest({ url: video.streamingPlaylists[0].playlistUrl, token: server.accessToken, expectedStatus })
await makeRawRequest({ url: video.streamingPlaylists[0].segmentsSha256Url, token: server.accessToken, expectedStatus })
}
describe('Live privacy update', function () {
let server: PeerTubeServer
before(async function () {
this.timeout(120000)
server = await createSingleServer(1)
await setAccessTokensToServers([ server ])
await setDefaultVideoChannel([ server ])
await server.config.enableMinimumTranscoding()
await server.config.enableLive({ allowReplay: true, transcoding: true, resolutions: 'min' })
})
describe('Normal live', function () {
let uuid: string
it('Should create a public live with private replay', async function () {
this.timeout(120000)
const fields: LiveVideoCreate = {
name: 'live',
privacy: VideoPrivacy.PUBLIC,
permanentLive: false,
replaySettings: { privacy: VideoPrivacy.PRIVATE },
saveReplay: true,
channelId: server.store.channel.id
}
const video = await server.live.create({ fields })
uuid = video.uuid
const ffmpegCommand = await server.live.sendRTMPStreamInVideo({ videoId: uuid })
await waitUntilLivePublishedOnAllServers([ server ], uuid)
await stopFfmpeg(ffmpegCommand)
await waitUntilLiveReplacedByReplayOnAllServers([ server ], uuid)
await waitJobs([ server ])
await testVideoFiles(server, uuid)
})
it('Should update the replay to public and re-update it to private', async function () {
this.timeout(120000)
await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PUBLIC } })
await waitJobs([ server ])
await testVideoFiles(server, uuid)
await server.videos.update({ id: uuid, attributes: { privacy: VideoPrivacy.PRIVATE } })
await waitJobs([ server ])
await testVideoFiles(server, uuid)
})
})
after(async function () {
await cleanupTests([ server ])
})
})

View File

@ -8,7 +8,12 @@ import { CONSTRAINTS_FIELDS } from '@server/initializers/constants.js'
import { getLocalVideoActivityPubUrl } from '@server/lib/activitypub/url.js'
import { federateVideoIfNeeded } from '@server/lib/activitypub/videos/index.js'
import { cleanupAndDestroyPermanentLive, cleanupTMPLiveFiles, cleanupUnsavedNormalLive } from '@server/lib/live/index.js'
import { generateHLSMasterPlaylistFilename, generateHlsSha256SegmentsFilename, getLiveReplayBaseDirectory } from '@server/lib/paths.js'
import {
generateHLSMasterPlaylistFilename,
generateHlsSha256SegmentsFilename,
getHLSDirectory,
getLiveReplayBaseDirectory
} from '@server/lib/paths.js'
import { generateLocalVideoMiniature, regenerateMiniaturesIfNeeded } from '@server/lib/thumbnail.js'
import { generateHlsPlaylistResolutionFromTS } from '@server/lib/transcoding/hls-transcoding.js'
import { VideoPathManager } from '@server/lib/video-path-manager.js'
@ -24,6 +29,7 @@ import { MVideo, MVideoLive, MVideoLiveSession, MVideoWithAllFiles } from '@serv
import { ffprobePromise, getAudioStream, getVideoStreamDimensionsInfo, getVideoStreamFPS } from '@peertube/peertube-ffmpeg'
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
import { JobQueue } from '../job-queue.js'
import { isVideoInPublicDirectory } from '@server/lib/video-privacy.js'
const lTags = loggerTagsFactory('live', 'job')
@ -139,9 +145,15 @@ async function saveReplayToExternalVideo (options: {
})
}
await assignReplayFilesToVideo({ video: replayVideo, replayDirectory })
const inputFileMutexReleaser = await VideoPathManager.Instance.lockFiles(liveVideo.uuid)
await remove(replayDirectory)
try {
await assignReplayFilesToVideo({ video: replayVideo, replayDirectory })
await remove(replayDirectory)
} finally {
inputFileMutexReleaser()
}
for (const type of [ ThumbnailType.MINIATURE, ThumbnailType.PREVIEW ]) {
const image = await generateLocalVideoMiniature({ video: replayVideo, videoFile: replayVideo.getMaxQualityFile(), type })
@ -160,11 +172,14 @@ async function replaceLiveByReplay (options: {
permanentLive: boolean
replayDirectory: string
}) {
const { video, liveSession, live, permanentLive, replayDirectory } = options
const { video: liveVideo, liveSession, live, permanentLive, replayDirectory } = options
const replaySettings = await VideoLiveReplaySettingModel.load(liveSession.replaySettingId)
const videoWithFiles = await VideoModel.loadFull(video.id)
const videoWithFiles = await VideoModel.loadFull(liveVideo.id)
const hlsPlaylist = videoWithFiles.getHLSPlaylist()
const replayInAnotherDirectory = isVideoInPublicDirectory(liveVideo.privacy) !== isVideoInPublicDirectory(replaySettings.privacy)
logger.info(`Replacing live ${liveVideo.uuid} by replay ${replayDirectory}.`, { replayInAnotherDirectory, ...lTags(liveVideo.uuid) })
await cleanupTMPLiveFiles(videoWithFiles, hlsPlaylist)
@ -188,13 +203,25 @@ async function replaceLiveByReplay (options: {
hlsPlaylist.segmentsSha256Filename = generateHlsSha256SegmentsFilename()
await hlsPlaylist.save()
await assignReplayFilesToVideo({ video: videoWithFiles, replayDirectory })
const inputFileMutexReleaser = await VideoPathManager.Instance.lockFiles(videoWithFiles.uuid)
// Should not happen in this function, but we keep the code if in the future we can replace the permanent live by a replay
if (permanentLive) { // Remove session replay
await remove(replayDirectory)
} else { // We won't stream again in this live, we can delete the base replay directory
await remove(getLiveReplayBaseDirectory(videoWithFiles))
try {
await assignReplayFilesToVideo({ video: videoWithFiles, replayDirectory })
// Should not happen in this function, but we keep the code if in the future we can replace the permanent live by a replay
if (permanentLive) { // Remove session replay
await remove(replayDirectory)
} else {
// We won't stream again in this live, we can delete the base replay directory
await remove(getLiveReplayBaseDirectory(liveVideo))
// If the live was in another base directory, also delete it
if (replayInAnotherDirectory) {
await remove(getHLSDirectory(liveVideo))
}
}
} finally {
inputFileMutexReleaser()
}
// Regenerate the thumbnail & preview?
@ -214,8 +241,10 @@ async function assignReplayFilesToVideo (options: {
const concatenatedTsFiles = await readdir(replayDirectory)
logger.info(`Assigning replays ${replayDirectory} to video ${video.uuid}.`, { concatenatedTsFiles, ...lTags(video.uuid) })
for (const concatenatedTsFile of concatenatedTsFiles) {
const inputFileMutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
// Generating hls playlist can be long, reload the video in this case
await video.reload()
const concatenatedTsFilePath = join(replayDirectory, concatenatedTsFile)
@ -228,17 +257,17 @@ async function assignReplayFilesToVideo (options: {
try {
await generateHlsPlaylistResolutionFromTS({
video,
inputFileMutexReleaser,
inputFileMutexReleaser: null, // Already locked in parent
concatenatedTsFilePath,
resolution,
fps,
isAAC: audioStream?.codec_name === 'aac'
})
logger.error('coucou')
} catch (err) {
logger.error('Cannot generate HLS playlist resolution from TS files.', { err })
}
inputFileMutexReleaser()
}
return video

View File

@ -58,8 +58,9 @@ export async function onHLSVideoFileTranscoding (options: {
videoFile: MVideoFile
videoOutputPath: string
m3u8OutputPath: string
filesLockedInParent?: boolean // default false
}) {
const { video, videoFile, videoOutputPath, m3u8OutputPath } = options
const { video, videoFile, videoOutputPath, m3u8OutputPath, filesLockedInParent = false } = options
// Create or update the playlist
const playlist = await retryTransactionWrapper(() => {
@ -69,7 +70,9 @@ export async function onHLSVideoFileTranscoding (options: {
})
videoFile.videoStreamingPlaylistId = playlist.id
const mutexReleaser = await VideoPathManager.Instance.lockFiles(video.uuid)
const mutexReleaser = !filesLockedInParent
? await VideoPathManager.Instance.lockFiles(video.uuid)
: null
try {
await video.reload()
@ -114,7 +117,7 @@ export async function onHLSVideoFileTranscoding (options: {
return { resolutionPlaylistPath, videoFile: savedVideoFile }
} finally {
mutexReleaser()
if (mutexReleaser) mutexReleaser()
}
}
@ -176,5 +179,11 @@ async function generateHlsPlaylistCommon (options: {
fps: -1
})
await onHLSVideoFileTranscoding({ video, videoFile: newVideoFile, videoOutputPath, m3u8OutputPath })
await onHLSVideoFileTranscoding({
video,
videoFile: newVideoFile,
videoOutputPath,
m3u8OutputPath,
filesLockedInParent: !inputFileMutexReleaser
})
}