|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- import { MutexInterface } from 'async-mutex'
- import { Job } from 'bullmq'
- import { ensureDir, move } from 'fs-extra/esm'
- import { join } from 'path'
- import { pick } from '@peertube/peertube-core-utils'
- import { retryTransactionWrapper } from '@server/helpers/database-utils.js'
- import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent.js'
- import { sequelizeTypescript } from '@server/initializers/database.js'
- import { MVideo } from '@server/types/models/index.js'
- import { getVideoStreamDuration } from '@peertube/peertube-ffmpeg'
- import { CONFIG } from '../../initializers/config.js'
- import { VideoFileModel } from '../../models/video/video-file.js'
- import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist.js'
- import { renameVideoFileInPlaylist, updatePlaylistAfterFileChange } from '../hls.js'
- import { generateHLSVideoFilename, getHlsResolutionPlaylistFilename } from '../paths.js'
- import { buildNewFile } from '../video-file.js'
- import { VideoPathManager } from '../video-path-manager.js'
- import { buildFFmpegVOD } from './shared/index.js'
-
- // Concat TS segments from a live video to a fragmented mp4 HLS playlist
- export async function generateHlsPlaylistResolutionFromTS (options: {
- video: MVideo
- concatenatedTsFilePath: string
- resolution: number
- fps: number
- isAAC: boolean
- inputFileMutexReleaser: MutexInterface.Releaser
- }) {
- return generateHlsPlaylistCommon({
- type: 'hls-from-ts' as 'hls-from-ts',
- inputPath: options.concatenatedTsFilePath,
-
- ...pick(options, [ 'video', 'resolution', 'fps', 'inputFileMutexReleaser', 'isAAC' ])
- })
- }
-
- // Generate an HLS playlist from an input file, and update the master playlist
- export function generateHlsPlaylistResolution (options: {
- video: MVideo
- videoInputPath: string
- resolution: number
- fps: number
- copyCodecs: boolean
- inputFileMutexReleaser: MutexInterface.Releaser
- job?: Job
- }) {
- return generateHlsPlaylistCommon({
- type: 'hls' as 'hls',
- inputPath: options.videoInputPath,
-
- ...pick(options, [ 'video', 'resolution', 'fps', 'copyCodecs', 'inputFileMutexReleaser', 'job' ])
- })
- }
-
- export async function onHLSVideoFileTranscoding (options: {
- video: MVideo
- videoOutputPath: string
- m3u8OutputPath: string
- filesLockedInParent?: boolean // default false
- }) {
- const { video, videoOutputPath, m3u8OutputPath, filesLockedInParent = false } = options
-
- // Create or update the playlist
- const playlist = await retryTransactionWrapper(() => {
- return sequelizeTypescript.transaction(async transaction => {
- return VideoStreamingPlaylistModel.loadOrGenerate(video, transaction)
- })
- })
-
- const newVideoFile = await buildNewFile({ mode: 'hls', path: videoOutputPath })
- newVideoFile.videoStreamingPlaylistId = playlist.id
-
- const mutexReleaser = !filesLockedInParent
- ? await VideoPathManager.Instance.lockFiles(video.uuid)
- : null
-
- try {
- await video.reload()
-
- const videoFilePath = VideoPathManager.Instance.getFSVideoFileOutputPath(playlist, newVideoFile)
- await ensureDir(VideoPathManager.Instance.getFSHLSOutputPath(video))
-
- // Move playlist file
- const resolutionPlaylistPath = VideoPathManager.Instance.getFSHLSOutputPath(
- video,
- getHlsResolutionPlaylistFilename(newVideoFile.filename)
- )
- await move(m3u8OutputPath, resolutionPlaylistPath, { overwrite: true })
-
- // Move video file
- await move(videoOutputPath, videoFilePath, { overwrite: true })
-
- await renameVideoFileInPlaylist(resolutionPlaylistPath, newVideoFile.filename)
-
- // Update video duration if it was not set (in case of a live for example)
- if (!video.duration) {
- video.duration = await getVideoStreamDuration(videoFilePath)
- await video.save()
- }
-
- await createTorrentAndSetInfoHash(playlist, newVideoFile)
-
- const oldFile = await VideoFileModel.loadHLSFile({
- playlistId: playlist.id,
- fps: newVideoFile.fps,
- resolution: newVideoFile.resolution
- })
-
- if (oldFile) {
- await video.removeStreamingPlaylistVideoFile(playlist, oldFile)
- await oldFile.destroy()
- }
-
- const savedVideoFile = await VideoFileModel.customUpsert(newVideoFile, 'streaming-playlist', undefined)
-
- await updatePlaylistAfterFileChange(video, playlist)
-
- return { resolutionPlaylistPath, videoFile: savedVideoFile }
- } finally {
- if (mutexReleaser) mutexReleaser()
- }
- }
-
- // ---------------------------------------------------------------------------
-
- async function generateHlsPlaylistCommon (options: {
- type: 'hls' | 'hls-from-ts'
- video: MVideo
- inputPath: string
-
- resolution: number
- fps: number
-
- inputFileMutexReleaser: MutexInterface.Releaser
-
- copyCodecs?: boolean
- isAAC?: boolean
-
- job?: Job
- }) {
- const { type, video, inputPath, resolution, fps, copyCodecs, isAAC, job, inputFileMutexReleaser } = options
- const transcodeDirectory = CONFIG.STORAGE.TMP_DIR
-
- const videoTranscodedBasePath = join(transcodeDirectory, type)
- await ensureDir(videoTranscodedBasePath)
-
- const videoFilename = generateHLSVideoFilename(resolution)
- const videoOutputPath = join(videoTranscodedBasePath, videoFilename)
-
- const resolutionPlaylistFilename = getHlsResolutionPlaylistFilename(videoFilename)
- const m3u8OutputPath = join(videoTranscodedBasePath, resolutionPlaylistFilename)
-
- const transcodeOptions = {
- type,
-
- inputPath,
- outputPath: m3u8OutputPath,
-
- resolution,
- fps,
- copyCodecs,
-
- isAAC,
-
- inputFileMutexReleaser,
-
- hlsPlaylist: {
- videoFilename
- }
- }
-
- await buildFFmpegVOD(job).transcode(transcodeOptions)
-
- await onHLSVideoFileTranscoding({
- video,
- videoOutputPath,
- m3u8OutputPath,
- filesLockedInParent: !inputFileMutexReleaser
- })
- }
|