import { Job } from 'bull' ; import { copyFile , ensureDir , move , remove , stat } from 'fs-extra' ; import { basename , extname as extnameUtil , join } from 'path' ; import { toEven } from '@server/helpers/core-utils' ; import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' ; import { MStreamingPlaylistFilesVideo , MVideoFile , MVideoFullLight } from '@server/types/models' ; import { VideoResolution , VideoStorage } from '../../../shared/models/videos' ; import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' ; import { transcode , TranscodeOptions , TranscodeOptionsType } from '../../helpers/ffmpeg-utils' ; import { canDoQuickTranscode , getDurationFromVideoFile , getMetadataFromFile , getVideoFileFPS } from '../../helpers/ffprobe-utils' ; import { CONFIG } from '../../initializers/config' ; import { P2P_MEDIA_LOADER_PEER_VERSION } from '../../initializers/constants' ; import { VideoFileModel } from '../../models/video/video-file' ; import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' ; import { updateMasterHLSPlaylist , updateSha256VODSegments } from '../hls' ; import { generateHLSMasterPlaylistFilename , generateHlsSha256SegmentsFilename , generateHLSVideoFilename , generateWebTorrentVideoFilename , getHlsResolutionPlaylistFilename } from '../paths' ; import { VideoPathManager } from '../video-path-manager' ; import { VideoTranscodingProfilesManager } from './video-transcoding-profiles' ; /**
 *
 * Functions that run transcoding functions, update the database, cleanup files, create torrent files...
 * Mainly called by the job queue
 *
 */ function optimizeOriginalVideofile ( video , inputVideoFile , job ? ) { const transcodeDirectory = CONFIG . STORAGE . TMP_DIR ; const newExtname = '.mp4' ; return VideoPathManager . Instance . makeAvailableVideoFile ( inputVideoFile . withVideoOrPlaylist ( video ) , async ( videoInputPath ) => { const videoTranscodedPath = join ( transcodeDirectory , video . id + '-transcoded' + newExtname ) ; const transcodeType = await canDoQuickTranscode ( videoInputPath ) ? 'quick-transcode' : 'video' ; const resolution = toEven ( inputVideoFile . resolution ) ; const transcodeOptions = { type : transcodeType , inputPath : videoInputPath , outputPath : videoTranscodedPath , availableEncoders : VideoTranscodingProfilesManager . Instance . getAvailableEncoders ( ) , profile : CONFIG . TRANSCODING . PROFILE , resolution , job } ; await transcode ( transcodeOptions ) ; inputVideoFile . extname = newExtname ; inputVideoFile . filename = generateWebTorrentVideoFilename ( resolution , newExtname ) ; inputVideoFile . storage = VideoStorage . FILE_SYSTEM ; const videoOutputPath = VideoPathManager . Instance . getFSVideoFileOutputPath ( video , inputVideoFile ) ; const { videoFile } = await onWebTorrentVideoFileTranscoding ( video , inputVideoFile , videoTranscodedPath , videoOutputPath ) ; await remove ( videoInputPath ) ; return { transcodeType , videoFile } ; } ) ; } function transcodeNewWebTorrentResolution ( video , resolution , isPortrait , job ) { const transcodeDirectory = CONFIG . STORAGE . TMP_DIR ; const extname = '.mp4' ; return VideoPathManager . Instance . makeAvailableVideoFile ( video . getMaxQualityFile ( ) . withVideoOrPlaylist ( video ) , async ( videoInputPath ) => { const newVideoFile = new VideoFileModel ( { resolution , extname , filename : generateWebTorrentVideoFilename ( resolution , extname ) , size : 0 , videoId : video . id } ) ; const videoOutputPath = VideoPathManager . Instance . getFSVideoFileOutputPath ( video , newVideoFile ) ; const videoTranscodedPath = join ( transcodeDirectory , newVideoFile . filename ) ; const transcodeOptions = resolution === VideoResolution . H_NOVIDEO ? { type : 'only-audio' as 'only-audio' , inputPath : videoInputPath , outputPath : videoTranscodedPath , availableEncoders : VideoTranscodingProfilesManager . Instance . getAvailableEncoders ( ) , profile : CONFIG . TRANSCODING . PROFILE , resolution , job } : { type : 'video' as 'video' , inputPath : videoInputPath , outputPath : videoTranscodedPath , availableEncoders : VideoTranscodingProfilesManager . Instance . getAvailableEncoders ( ) , profile : CONFIG . TRANSCODING . PROFILE , resolution , isPortraitMode : isPortrait , job } ; await transcode ( transcodeOptions ) ; return onWebTorrentVideoFileTranscoding ( video , newVideoFile , videoTranscodedPath , videoOutputPath ) ; } ) ; } function mergeAudioVideofile ( video , resolution , job ) { const transcodeDirectory = CONFIG . STORAGE . TMP_DIR ; const newExtname = '.mp4' ; const inputVideoFile = video . getMinQualityFile ( ) ; return VideoPathManager . Instance . makeAvailableVideoFile ( inputVideoFile . withVideoOrPlaylist ( video ) , async ( audioInputPath ) => { const videoTranscodedPath = join ( transcodeDirectory , video . id + '-transcoded' + newExtname ) ; const previewPath = video . getPreview ( ) . getPath ( ) ; const tmpPreviewPath = join ( CONFIG . STORAGE . TMP_DIR , basename ( previewPath ) ) ; await copyFile ( previewPath , tmpPreviewPath ) ; const transcodeOptions = { type : 'merge-audio' as 'merge-audio' , inputPath : tmpPreviewPath , outputPath : videoTranscodedPath , availableEncoders : VideoTranscodingProfilesManager . Instance . getAvailableEncoders ( ) , profile : CONFIG . TRANSCODING . PROFILE , audioPath : audioInputPath , resolution , job } ; try { await transcode ( transcodeOptions ) ; await remove ( audioInputPath ) ; await remove ( tmpPreviewPath ) ; } catch ( err ) { await remove ( tmpPreviewPath ) ; throw err ; } inputVideoFile . extname = newExtname ; inputVideoFile . resolution = resolution ; inputVideoFile . filename = generateWebTorrentVideoFilename ( inputVideoFile . resolution , newExtname ) ; const videoOutputPath = VideoPathManager . Instance . getFSVideoFileOutputPath ( video , inputVideoFile ) ; video . duration = await getDurationFromVideoFile ( videoTranscodedPath ) ; await video . save ( ) ; return onWebTorrentVideoFileTranscoding ( video , inputVideoFile , videoTranscodedPath , videoOutputPath ) ; } ) ; } async function generateHlsPlaylistResolutionFromTS ( options ) { return generateHlsPlaylistCommon ( { video : options . video , resolution : options . resolution , isPortraitMode : options . isPortraitMode , inputPath : options . concatenatedTsFilePath , type : 'hls-from-ts' as 'hls-from-ts' , isAAC : options . isAAC } ) ; } function generateHlsPlaylistResolution ( options ) { return generateHlsPlaylistCommon ( { video : options . video , resolution : options . resolution , copyCodecs : options . copyCodecs , isPortraitMode : options . isPortraitMode , inputPath : options . videoInputPath , type : 'hls' as 'hls' , job : options . job } ) ; } export { generateHlsPlaylistResolution , generateHlsPlaylistResolutionFromTS , optimizeOriginalVideofile , transcodeNewWebTorrentResolution , mergeAudioVideofile } ; async function onWebTorrentVideoFileTranscoding ( video , videoFile , transcodingPath , outputPath ) { const stats = await stat ( transcodingPath ) ; const fps = await getVideoFileFPS ( transcodingPath ) ; const metadata = await getMetadataFromFile ( transcodingPath ) ; await move ( transcodingPath , outputPath , { overwrite : true } ) ; videoFile . size = stats . size ; videoFile . fps = fps ; videoFile . metadata = metadata ; await createTorrentAndSetInfoHash ( video , videoFile ) ; await VideoFileModel . customUpsert ( videoFile , 'video' , undefined ) ; video . VideoFiles = await video . $get ( 'VideoFiles' ) ; return { video , videoFile } ; } async function generateHlsPlaylistCommon ( options ) { const { type , video , inputPath , resolution , copyCodecs , isPortraitMode , isAAC , job } = options ; const transcodeDirectory = CONFIG . STORAGE . TMP_DIR ; const videoTranscodedBasePath = join ( transcodeDirectory , type ) ; await ensureDir ( videoTranscodedBasePath ) ; const videoFilename = generateHLSVideoFilename ( resolution ) ; const resolutionPlaylistFilename = getHlsResolutionPlaylistFilename ( videoFilename ) ; const resolutionPlaylistFileTranscodePath = join ( videoTranscodedBasePath , resolutionPlaylistFilename ) ; const transcodeOptions = { type , inputPath , outputPath : resolutionPlaylistFileTranscodePath , availableEncoders : VideoTranscodingProfilesManager . Instance . getAvailableEncoders ( ) , profile : CONFIG . TRANSCODING . PROFILE , resolution , copyCodecs , isPortraitMode , isAAC , hlsPlaylist : { videoFilename } , job } ; await transcode ( transcodeOptions ) ; const playlist = await VideoStreamingPlaylistModel . loadOrGenerate ( video ) ; if ( ! playlist . playlistFilename ) { playlist . playlistFilename = generateHLSMasterPlaylistFilename ( video . isLive ) ; } if ( ! playlist . segmentsSha256Filename ) { playlist . segmentsSha256Filename = generateHlsSha256SegmentsFilename ( video . isLive ) ; } playlist . p2pMediaLoaderInfohashes = [ ] ; playlist . p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION ; playlist . type = VideoStreamingPlaylistType . HLS ; await playlist . save ( ) ; const extname = extnameUtil ( videoFilename ) ; const newVideoFile = new VideoFileModel ( { resolution , extname , size : 0 , filename : videoFilename , fps : - 1 , videoStreamingPlaylistId : playlist . id } ) ; const videoFilePath = VideoPathManager . Instance . getFSVideoFileOutputPath ( playlist , newVideoFile ) ; await ensureDir ( VideoPathManager . Instance . getFSHLSOutputPath ( video ) ) ; const resolutionPlaylistPath = VideoPathManager . Instance . getFSHLSOutputPath ( video , resolutionPlaylistFilename ) ; await move ( resolutionPlaylistFileTranscodePath , resolutionPlaylistPath , { overwrite : true } ) ; await move ( join ( videoTranscodedBasePath , videoFilename ) , videoFilePath , { overwrite : true } ) ; const stats = await stat ( videoFilePath ) ; newVideoFile . size = stats . size ; newVideoFile . fps = await getVideoFileFPS ( videoFilePath ) ; newVideoFile . metadata = await getMetadataFromFile ( videoFilePath ) ; await createTorrentAndSetInfoHash ( playlist , newVideoFile ) ; const savedVideoFile = await VideoFileModel . customUpsert ( newVideoFile , 'streaming-playlist' , undefined ) ; const playlistWithFiles = playlist as MStreamingPlaylistFilesVideo ; playlistWithFiles . VideoFiles = await playlist . $get ( 'VideoFiles' ) ; playlist . assignP2PMediaLoaderInfoHashes ( video , playlistWithFiles . VideoFiles ) ; await playlist . save ( ) ; video . setHLSPlaylist ( playlist ) ; await updateMasterHLSPlaylist ( video , playlistWithFiles ) ; await updateSha256VODSegments ( video , playlistWithFiles ) ; return { resolutionPlaylistPath , videoFile : savedVideoFile } ; }