はじまりの大地
このコミットが含まれているのは:
@@ -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) {
|
||||
const videoStream = await getVideoStream(path)
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { FFmpegImage } from '@peertube/peertube-ffmpeg'
|
||||
import { getFFmpegCommandWrapperOptions } from './ffmpeg-options.js'
|
||||
|
||||
export function processGIF (options: Parameters<FFmpegImage['processGIF']>[0]) {
|
||||
return new FFmpegImage(getFFmpegCommandWrapperOptions('thumbnail')).processGIF(options)
|
||||
}
|
||||
|
||||
export function generateThumbnailFromVideo (options: Parameters<FFmpegImage['generateThumbnailFromVideo']>[0]) {
|
||||
return new FFmpegImage(getFFmpegCommandWrapperOptions('thumbnail')).generateThumbnailFromVideo(options)
|
||||
}
|
||||
|
||||
export function convertWebPToJPG (options: Parameters<FFmpegImage['convertWebPToJPG']>[0]) {
|
||||
return new FFmpegImage(getFFmpegCommandWrapperOptions('thumbnail')).convertWebPToJPG(options)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { VIDEO_TRANSCODING_FPS } from '@server/initializers/constants.js'
|
||||
|
||||
export function computeOutputFPS (options: {
|
||||
inputFPS: number
|
||||
resolution: number
|
||||
}) {
|
||||
const { resolution } = options
|
||||
|
||||
let fps = options.inputFPS
|
||||
|
||||
if (
|
||||
// On small/medium resolutions, limit FPS
|
||||
resolution !== undefined &&
|
||||
resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN &&
|
||||
fps > VIDEO_TRANSCODING_FPS.AVERAGE
|
||||
) {
|
||||
// Get closest standard framerate by modulo: downsampling has to be done to a divisor of the nominal fps value
|
||||
fps = getClosestFramerateStandard({ fps, type: 'STANDARD' })
|
||||
}
|
||||
|
||||
if (fps < VIDEO_TRANSCODING_FPS.HARD_MIN) {
|
||||
throw new Error(`Cannot compute FPS because ${fps} is lower than our minimum value ${VIDEO_TRANSCODING_FPS.HARD_MIN}`)
|
||||
}
|
||||
|
||||
// Cap min FPS
|
||||
if (fps < VIDEO_TRANSCODING_FPS.SOFT_MIN) fps = VIDEO_TRANSCODING_FPS.SOFT_MIN
|
||||
// Cap max FPS
|
||||
if (fps > VIDEO_TRANSCODING_FPS.SOFT_MAX) fps = getClosestFramerateStandard({ fps, type: 'HD_STANDARD' })
|
||||
|
||||
return fps
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function getClosestFramerateStandard (options: {
|
||||
fps: number
|
||||
type: 'HD_STANDARD' | 'STANDARD'
|
||||
}) {
|
||||
const { fps, type } = options
|
||||
|
||||
return VIDEO_TRANSCODING_FPS[type].slice(0)
|
||||
.sort((a, b) => fps % a - fps % b)[0]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './codecs.js'
|
||||
export * from './ffmpeg-image.js'
|
||||
export * from './ffmpeg-options.js'
|
||||
export * from './framerate.js'
|
||||
新しい課題から参照
ユーザをブロックする