import * as ffmpeg from 'fluent-ffmpeg'
-import { VideoFileMetadata } from '@shared/models/videos/video-file-metadata'
-import { getMaxBitrate, VideoResolution } from '../../shared/models/videos'
+import { getMaxBitrate } from '@shared/core-utils'
+import { VideoFileMetadata, VideoResolution, VideoTranscodingFPS } from '../../shared/models/videos'
import { CONFIG } from '../initializers/config'
import { VIDEO_TRANSCODING_FPS } from '../initializers/constants'
import { logger } from './logger'
}
}
-async function getVideoStreamSize (path: string, existingProbe?: ffmpeg.FfprobeData) {
+async function getVideoStreamSize (path: string, existingProbe?: ffmpeg.FfprobeData): Promise<{ width: number, height: number }> {
const videoStream = await getVideoStreamFromFile(path, existingProbe)
return videoStream === null
const videoCodec = videoStream.codec_tag_string
+ if (videoCodec === 'vp09') return 'vp09.00.50.08'
+ if (videoCodec === 'hev1') return 'hev1.1.6.L93.B0'
+
const baseProfileMatrix = {
- High: '6400',
- Main: '4D40',
- Baseline: '42E0'
+ avc1: {
+ High: '6400',
+ Main: '4D40',
+ Baseline: '42E0'
+ },
+ av01: {
+ High: '1',
+ Main: '0',
+ Professional: '2'
+ }
}
- let baseProfile = baseProfileMatrix[videoStream.profile]
+ let baseProfile = baseProfileMatrix[videoCodec][videoStream.profile]
if (!baseProfile) {
logger.warn('Cannot get video profile codec of %s.', path, { videoStream })
- baseProfile = baseProfileMatrix['High'] // Fallback
+ baseProfile = baseProfileMatrix[videoCodec]['High'] // Fallback
+ }
+
+ if (videoCodec === 'av01') {
+ const level = videoStream.level
+
+ // Guess the tier indicator and bit depth
+ return `${videoCodec}.${baseProfile}.${level}M.08`
}
+ // Default, h264 codec
let level = videoStream.level.toString(16)
if (level.length === 1) level = `0${level}`
if (!audioStream) return ''
- const audioCodec = audioStream.codec_name
- if (audioCodec === 'aac') return 'mp4a.40.2'
+ const audioCodecName = audioStream.codec_name
+
+ if (audioCodecName === 'opus') return 'opus'
+ if (audioCodecName === 'vorbis') return 'vorbis'
+ if (audioCodecName === 'aac') return 'mp4a.40.2'
logger.warn('Cannot get audio codec of %s.', path, { audioStream })
const size = await getVideoStreamSize(path, existingProbe)
return {
- videoFileResolution: Math.min(size.height, size.width),
+ width: size.width,
+ height: size.height,
+ ratio: Math.max(size.height, size.width) / Math.min(size.height, size.width),
+ resolution: Math.min(size.height, size.width),
isPortraitMode: size.height > size.width
}
}
return new VideoFileMetadata(metadata)
}
-async function getVideoFileBitrate (path: string, existingProbe?: ffmpeg.FfprobeData) {
+async function getVideoFileBitrate (path: string, existingProbe?: ffmpeg.FfprobeData): Promise<number> {
const metadata = await getMetadataFromFile(path, existingProbe)
- return metadata.format.bit_rate as number
+ let bitrate = metadata.format.bit_rate as number
+ if (bitrate && !isNaN(bitrate)) return bitrate
+
+ const videoStream = await getVideoStreamFromFile(path, existingProbe)
+ if (!videoStream) return undefined
+
+ bitrate = videoStream?.bit_rate
+ if (bitrate && !isNaN(bitrate)) return bitrate
+
+ return undefined
}
async function getDurationFromVideoFile (path: string, existingProbe?: ffmpeg.FfprobeData) {
const metadata = await getMetadataFromFile(path, existingProbe)
- return Math.floor(metadata.format.duration)
+ return Math.round(metadata.format.duration)
}
async function getVideoStreamFromFile (path: string, existingProbe?: ffmpeg.FfprobeData) {
VideoResolution.H_720P,
VideoResolution.H_240P,
VideoResolution.H_1080P,
+ VideoResolution.H_1440P,
VideoResolution.H_4K
]
}
async function canDoQuickTranscode (path: string): Promise<boolean> {
+ if (CONFIG.TRANSCODING.PROFILE !== 'default') return false
+
const probe = await ffprobePromise(path)
return await canDoQuickVideoTranscode(path, probe) &&
const videoStream = await getVideoStreamFromFile(path, probe)
const fps = await getVideoFileFPS(path, probe)
const bitRate = await getVideoFileBitrate(path, probe)
- const resolution = await getVideoFileResolution(path, probe)
+ const resolutionData = await getVideoFileResolution(path, probe)
// If ffprobe did not manage to guess the bitrate
if (!bitRate) return false
if (videoStream['codec_name'] !== 'h264') return false
if (videoStream['pix_fmt'] !== 'yuv420p') return false
if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false
- if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false
+ if (bitRate > getMaxBitrate({ ...resolutionData, fps })) return false
return true
}
const maxAudioBitrate = getMaxAudioBitrate('aac', audioBitrate)
if (maxAudioBitrate !== -1 && audioBitrate > maxAudioBitrate) return false
+ const channelLayout = parsedAudio.audioStream['channel_layout']
+ // Causes playback issues with Chrome
+ if (!channelLayout || channelLayout === 'unknown') return false
+
return true
}
-function getClosestFramerateStandard (fps: number, type: 'HD_STANDARD' | 'STANDARD'): number {
+function getClosestFramerateStandard <K extends keyof Pick<VideoTranscodingFPS, 'HD_STANDARD' | 'STANDARD'>> (fps: number, type: K) {
return VIDEO_TRANSCODING_FPS[type].slice(0)
.sort((a, b) => fps % a - fps % b)[0]
}