X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Flib%2Fvideo-transcoding.ts;h=8d786e0ef5ea5ba16e7f79e96557072dbc4af98b;hb=536598cfafab1c5e24e881db1c528489f804fb6b;hp=bf3ff78c2ac233569a2815bf8cf6ed91a7dcfe04;hpb=0491173a61aed66205c017e0d7e0503ea316c144;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index bf3ff78c2..8d786e0ef 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts @@ -1,22 +1,37 @@ -import { CONFIG } from '../initializers' -import { join, extname } from 'path' -import { getVideoFileFPS, getVideoFileResolution, transcode } from '../helpers/ffmpeg-utils' -import { copy, remove, rename, stat } from 'fs-extra' +import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants' +import { join } from 'path' +import { canDoQuickTranscode, getVideoFileFPS, transcode, TranscodeOptions, TranscodeOptionsType } from '../helpers/ffmpeg-utils' +import { ensureDir, move, remove, stat } from 'fs-extra' import { logger } from '../helpers/logger' import { VideoResolution } from '../../shared/models/videos' import { VideoFileModel } from '../models/video/video-file' import { VideoModel } from '../models/video/video' - -async function optimizeOriginalVideofile (video: VideoModel) { +import { updateMasterHLSPlaylist, updateSha256Segments } from './hls' +import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' +import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' +import { CONFIG } from '../initializers/config' + +/** + * Optimize the original video file and replace it. The resolution is not changed. + */ +async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR + const transcodeDirectory = CONFIG.STORAGE.TMP_DIR const newExtname = '.mp4' - const inputVideoFile = video.getOriginalFile() + + const inputVideoFile = inputVideoFileArg ? inputVideoFileArg : video.getOriginalFile() const videoInputPath = join(videosDirectory, video.getVideoFilename(inputVideoFile)) - const videoTranscodedPath = join(videosDirectory, video.id + '-transcoded' + newExtname) + const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) - const transcodeOptions = { + const transcodeType: TranscodeOptionsType = await canDoQuickTranscode(videoInputPath) + ? 'quick-transcode' + : 'video' + + const transcodeOptions: TranscodeOptions = { + type: transcodeType as any, // FIXME: typing issue inputPath: videoInputPath, - outputPath: videoTranscodedPath + outputPath: videoTranscodedPath, + resolution: inputVideoFile.resolution } // Could be very long! @@ -26,18 +41,11 @@ async function optimizeOriginalVideofile (video: VideoModel) { await remove(videoInputPath) // Important to do this before getVideoFilename() to take in account the new file extension - inputVideoFile.set('extname', newExtname) + inputVideoFile.extname = newExtname const videoOutputPath = video.getVideoFilePath(inputVideoFile) - await rename(videoTranscodedPath, videoOutputPath) - const stats = await stat(videoOutputPath) - const fps = await getVideoFileFPS(videoOutputPath) - inputVideoFile.set('size', stats.size) - inputVideoFile.set('fps', fps) - - await video.createTorrentAndSetInfoHash(inputVideoFile) - await inputVideoFile.save() + await onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) } catch (err) { // Auto destruction... video.destroy().catch(err => logger.error('Cannot destruct video after transcoding failure.', { err })) @@ -46,8 +54,12 @@ async function optimizeOriginalVideofile (video: VideoModel) { } } -async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { +/** + * Transcode the original video file to a lower resolution. + */ +async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) { const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR + const transcodeDirectory = CONFIG.STORAGE.TMP_DIR const extname = '.mp4' // We are sure it's x264 in mp4 because optimizeOriginalVideofile was already executed @@ -59,72 +71,117 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR size: 0, videoId: video.id }) - const videoOutputPath = join(videosDirectory, video.getVideoFilename(newVideoFile)) + const videoOutputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(newVideoFile)) + const videoTranscodedPath = join(transcodeDirectory, video.getVideoFilename(newVideoFile)) const transcodeOptions = { + type: 'video' as 'video', inputPath: videoInputPath, - outputPath: videoOutputPath, + outputPath: videoTranscodedPath, resolution, - isPortraitMode + isPortraitMode: isPortrait } await transcode(transcodeOptions) - const stats = await stat(videoOutputPath) - const fps = await getVideoFileFPS(videoOutputPath) + return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) +} - newVideoFile.set('size', stats.size) - newVideoFile.set('fps', fps) +async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) { + const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR + const transcodeDirectory = CONFIG.STORAGE.TMP_DIR + const newExtname = '.mp4' - await video.createTorrentAndSetInfoHash(newVideoFile) + const inputVideoFile = video.getOriginalFile() - await newVideoFile.save() + const audioInputPath = join(videosDirectory, video.getVideoFilename(video.getOriginalFile())) + const videoTranscodedPath = join(transcodeDirectory, video.id + '-transcoded' + newExtname) - video.VideoFiles.push(newVideoFile) -} + const transcodeOptions = { + type: 'merge-audio' as 'merge-audio', + inputPath: video.getPreview().getPath(), + outputPath: videoTranscodedPath, + audioPath: audioInputPath, + resolution + } -async function importVideoFile (video: VideoModel, inputFilePath: string) { - const { videoFileResolution } = await getVideoFileResolution(inputFilePath) - const { size } = await stat(inputFilePath) - const fps = await getVideoFileFPS(inputFilePath) + await transcode(transcodeOptions) - let updatedVideoFile = new VideoFileModel({ - resolution: videoFileResolution, - extname: extname(inputFilePath), - size, - fps, - videoId: video.id - }) + await remove(audioInputPath) + + // Important to do this before getVideoFilename() to take in account the new file extension + inputVideoFile.extname = newExtname - const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution) + const videoOutputPath = video.getVideoFilePath(inputVideoFile) + + return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) +} - if (currentVideoFile) { - // Remove old file and old torrent - await video.removeFile(currentVideoFile) - await video.removeTorrent(currentVideoFile) - // Remove the old video file from the array - video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) +async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { + const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) + await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)) - // Update the database - currentVideoFile.set('extname', updatedVideoFile.extname) - currentVideoFile.set('size', updatedVideoFile.size) - currentVideoFile.set('fps', updatedVideoFile.fps) + const videoInputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(video.getOriginalFile())) + const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) - updatedVideoFile = currentVideoFile + const transcodeOptions = { + type: 'hls' as 'hls', + inputPath: videoInputPath, + outputPath, + resolution, + isPortraitMode, + + hlsPlaylist: { + videoFilename: VideoStreamingPlaylistModel.getHlsVideoName(video.uuid, resolution) + } } - const outputPath = video.getVideoFilePath(updatedVideoFile) - await copy(inputFilePath, outputPath) + await transcode(transcodeOptions) - await video.createTorrentAndSetInfoHash(updatedVideoFile) + await updateMasterHLSPlaylist(video) + await updateSha256Segments(video) - await updatedVideoFile.save() + const playlistUrl = WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsMasterPlaylistStaticPath(video.uuid) - video.VideoFiles.push(updatedVideoFile) + await VideoStreamingPlaylistModel.upsert({ + videoId: video.id, + playlistUrl, + segmentsSha256Url: WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsSha256SegmentsStaticPath(video.uuid), + p2pMediaLoaderInfohashes: VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlistUrl, video.VideoFiles), + p2pMediaLoaderPeerVersion: P2P_MEDIA_LOADER_PEER_VERSION, + + type: VideoStreamingPlaylistType.HLS + }) } +// --------------------------------------------------------------------------- + export { - optimizeOriginalVideofile, + generateHlsPlaylist, + optimizeVideofile, transcodeOriginalVideofile, - importVideoFile + mergeAudioVideofile +} + +// --------------------------------------------------------------------------- + +async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) { + const stats = await stat(transcodingPath) + const fps = await getVideoFileFPS(transcodingPath) + + await move(transcodingPath, outputPath) + + videoFile.set('size', stats.size) + videoFile.set('fps', fps) + + await video.createTorrentAndSetInfoHash(videoFile) + + const updatedVideoFile = await videoFile.save() + + // Add it if this is a new created file + if (video.VideoFiles.some(f => f.id === videoFile.id) === false) { + video.VideoFiles.push(updatedVideoFile) + } + + return video }