Init commit

This commit is contained in:
ShreejitPanchal
2026-05-19 19:56:02 +08:00
commit 6601501eff
4047 changed files with 2185372 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
import { FfprobeData } from 'fluent-ffmpeg'
import { getAudioStream, getVideoStream } from '@peertube/peertube-ffmpeg'
import { logger } from '../logger.js'
import { forceNumber } from '@peertube/peertube-core-utils'
export async function getVideoStreamCodec (path: string, existingProbe?: FfprobeData) {
const videoStream = await getVideoStream(path, existingProbe)
if (!videoStream) return ''
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 = {
avc1: {
High: '6400',
Main: '4D40',
Baseline: '42E0'
},
av01: {
High: '1',
Main: '0',
Professional: '2'
}
}
let baseProfile = baseProfileMatrix[videoCodec][videoStream.profile]
if (!baseProfile) {
logger.warn('Cannot get video profile codec of %s.', path, { videoStream })
baseProfile = baseProfileMatrix[videoCodec]['High'] // Fallback
}
if (videoCodec === 'av01') {
let level = videoStream.level.toString()
if (level.length === 1) level = `0${level}`
// Guess the tier indicator and bit depth
return `${videoCodec}.${baseProfile}.${level}M.08`
}
let level = forceNumber(videoStream.level).toString(16)
if (level.length === 1) level = `0${level}`
// Default, h264 codec
return `${videoCodec}.${baseProfile}${level}`
}
export async function getAudioStreamCodec (path: string, existingProbe?: FfprobeData) {
const { audioStream } = await getAudioStream(path, existingProbe)
if (!audioStream) return ''
const audioCodecName = audioStream.codec_name
if (audioCodecName === 'opus') return 'opus'
if (audioCodecName === 'vorbis') return 'vorbis'
if (audioCodecName === 'aac') return 'mp4a.40.2'
if (audioCodecName === 'mp3') return 'mp4a.40.34'
logger.warn('Cannot get audio codec of %s.', path, { audioStream })
return 'mp4a.40.2' // Fallback
}

View File

@@ -0,0 +1,10 @@
import { FFmpegImage } from '@peertube/peertube-ffmpeg'
import { getFFmpegCommandWrapperOptions } from './ffmpeg-options.js'
export function processImage (options: Parameters<FFmpegImage['processImage']>[0]) {
return new FFmpegImage(getFFmpegCommandWrapperOptions('thumbnail')).processImage(options)
}
export function generateThumbnailFromVideo (options: Parameters<FFmpegImage['generateThumbnailFromVideo']>[0]) {
return new FFmpegImage(getFFmpegCommandWrapperOptions('thumbnail')).generateThumbnailFromVideo(options)
}

View File

@@ -0,0 +1,45 @@
import { logger } from '@server/helpers/logger.js'
import { CONFIG } from '@server/initializers/config.js'
import { FFMPEG_NICE } from '@server/initializers/constants.js'
import { FFmpegCommandWrapperOptions } from '@peertube/peertube-ffmpeg'
import { AvailableEncoders } from '@peertube/peertube-models'
type CommandType = 'live' | 'vod' | 'thumbnail'
export function getFFmpegCommandWrapperOptions (type: CommandType, availableEncoders?: AvailableEncoders): FFmpegCommandWrapperOptions {
return {
availableEncoders,
profile: getProfile(type),
niceness: FFMPEG_NICE[type.toUpperCase()],
tmpDirectory: CONFIG.STORAGE.TMP_DIR,
threads: getThreads(type),
logger: {
debug: logger.debug.bind(logger),
info: logger.info.bind(logger),
warn: logger.warn.bind(logger),
error: logger.error.bind(logger)
},
lTags: { tags: [ 'ffmpeg' ] }
}
}
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
function getThreads (type: CommandType) {
if (type === 'live') return CONFIG.LIVE.TRANSCODING.THREADS
if (type === 'vod') return CONFIG.TRANSCODING.THREADS
// Auto
return 0
}
function getProfile (type: CommandType) {
if (type === 'live') return CONFIG.LIVE.TRANSCODING.PROFILE
if (type === 'vod') return CONFIG.TRANSCODING.PROFILE
return undefined
}

View File

@@ -0,0 +1,93 @@
import { VideoResolution } from '@peertube/peertube-models'
import { CONFIG } from '@server/initializers/config.js'
import { logger } from '../logger.js'
export function computeOutputFPS (options: {
inputFPS: number
isOriginResolution: boolean
resolution: number
type: 'vod' | 'live'
}) {
const { resolution, isOriginResolution, type } = options
if (resolution === VideoResolution.H_NOVIDEO) return 0
const settings = type === 'vod'
? buildTranscodingFPSOptions(CONFIG.TRANSCODING.FPS.MAX)
: buildTranscodingFPSOptions(CONFIG.LIVE.TRANSCODING.FPS.MAX)
let fps = options.inputFPS
if (
// On small/medium transcoded resolutions, limit FPS
!isOriginResolution &&
resolution !== undefined &&
resolution < settings.KEEP_ORIGIN_FPS_RESOLUTION_MIN &&
fps > settings.AVERAGE
) {
// Get closest standard framerate by modulo: downsampling has to be done to a divisor of the nominal fps value
fps = getClosestFramerate({ fps, settings, type: 'STANDARD' })
}
if (fps < settings.HARD_MIN) {
throw new Error(`Cannot compute FPS because ${fps} is lower than our minimum value ${settings.HARD_MIN}`)
}
// Cap min FPS
fps = Math.max(fps, settings.TRANSCODED_MIN)
// Cap max FPS for non-origin video file
if (!isOriginResolution && fps > settings.TRANSCODED_MAX) {
fps = getClosestFramerate({ fps, settings, type: 'HD_STANDARD' })
}
logger.debug(`Computed output FPS ${fps} for resolution ${resolution}p`, { options, settings })
return fps
}
// ---------------------------------------------------------------------------
// Private
// ---------------------------------------------------------------------------
function buildTranscodingFPSOptions (maxFPS: number) {
const STANDARD = [ 24, 25, 30 ].filter(v => v <= maxFPS)
if (STANDARD.length === 0) STANDARD.push(maxFPS)
const HD_STANDARD = [ 50, 60, maxFPS ].filter(v => v <= maxFPS)
return {
HARD_MIN: 0.1,
TRANSCODED_MIN: 1,
TRANSCODED_MAX: maxFPS,
STANDARD,
HD_STANDARD,
AVERAGE: Math.min(30, maxFPS),
KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum)
}
}
function getClosestFramerate (options: {
fps: number
settings: ReturnType<typeof buildTranscodingFPSOptions>
type: Extract<keyof ReturnType<typeof buildTranscodingFPSOptions>, 'HD_STANDARD' | 'STANDARD'>
}) {
const { fps, settings, type } = options
const copy = [ ...settings[type] ]
// Biggest FPS first
const descSorted = copy.sort((a, b) => b - a)
// Find biggest FPS that can be divided by input FPS
const found = descSorted.find(e => fps % e === 0)
if (found) return found
// Approximation to the best result
return copy.sort((a, b) => fps % a - fps % b)[0]
}

View File

@@ -0,0 +1,4 @@
export * from './codecs.js'
export * from './ffmpeg-image.js'
export * from './ffmpeg-options.js'
export * from './framerate.js'