+import { checkFFmpegEncoders } from '../initializers/checker-before-init'
+import { readFile, remove, writeFile } from 'fs-extra'
+import { CONFIG } from '../initializers/config'
+import { VideoFileMetadata } from '@shared/models/videos/video-file-metadata'
+
+/**
+ * A toolbox to play with audio
+ */
+namespace audio {
+ export const get = (videoPath: string) => {
+ // without position, ffprobe considers the last input only
+ // we make it consider the first input only
+ // if you pass a file path to pos, then ffprobe acts on that file directly
+ return new Promise<{ absolutePath: string, audioStream?: any }>((res, rej) => {
+
+ function parseFfprobe (err: any, data: ffmpeg.FfprobeData) {
+ if (err) return rej(err)
+
+ if ('streams' in data) {
+ const audioStream = data.streams.find(stream => stream['codec_type'] === 'audio')
+ if (audioStream) {
+ return res({
+ absolutePath: data.format.filename,
+ audioStream
+ })
+ }
+ }
+
+ return res({ absolutePath: data.format.filename })
+ }
+
+ return ffmpeg.ffprobe(videoPath, parseFfprobe)
+ })
+ }
+
+ export namespace bitrate {
+ const baseKbitrate = 384
+
+ const toBits = (kbits: number) => kbits * 8000
+
+ export const aac = (bitrate: number): number => {
+ switch (true) {
+ case bitrate > toBits(baseKbitrate):
+ return baseKbitrate
+
+ default:
+ return -1 // we interpret it as a signal to copy the audio stream as is
+ }
+ }
+
+ export const mp3 = (bitrate: number): number => {
+ /*
+ a 192kbit/sec mp3 doesn't hold as much information as a 192kbit/sec aac.
+ That's why, when using aac, we can go to lower kbit/sec. The equivalences
+ made here are not made to be accurate, especially with good mp3 encoders.
+ */
+ switch (true) {
+ case bitrate <= toBits(192):
+ return 128
+
+ case bitrate <= toBits(384):
+ return 256
+
+ default:
+ return baseKbitrate
+ }
+ }
+ }
+}
+
+function computeResolutionsToTranscode (videoFileResolution: number) {
+ const resolutionsEnabled: number[] = []
+ const configResolutions = CONFIG.TRANSCODING.RESOLUTIONS
+
+ // Put in the order we want to proceed jobs
+ const resolutions = [
+ VideoResolution.H_NOVIDEO,
+ VideoResolution.H_480P,
+ VideoResolution.H_360P,
+ VideoResolution.H_720P,
+ VideoResolution.H_240P,
+ VideoResolution.H_1080P,
+ VideoResolution.H_4K
+ ]
+
+ for (const resolution of resolutions) {
+ if (configResolutions[resolution + 'p'] === true && videoFileResolution > resolution) {
+ resolutionsEnabled.push(resolution)
+ }
+ }
+
+ return resolutionsEnabled
+}
+
+async function getVideoStreamSize (path: string) {
+ const videoStream = await getVideoStreamFromFile(path)
+
+ return videoStream === null
+ ? { width: 0, height: 0 }
+ : { width: videoStream.width, height: videoStream.height }
+}
+
+async function getVideoStreamCodec (path: string) {
+ const videoStream = await getVideoStreamFromFile(path)
+
+ if (!videoStream) return ''
+
+ const videoCodec = videoStream.codec_tag_string
+
+ const baseProfileMatrix = {
+ High: '6400',
+ Main: '4D40',
+ Baseline: '42E0'
+ }
+
+ let baseProfile = baseProfileMatrix[videoStream.profile]
+ if (!baseProfile) {
+ logger.warn('Cannot get video profile codec of %s.', path, { videoStream })
+ baseProfile = baseProfileMatrix['High'] // Fallback
+ }
+
+ let level = videoStream.level.toString(16)
+ if (level.length === 1) level = `0${level}`
+
+ return `${videoCodec}.${baseProfile}${level}`
+}
+
+async function getAudioStreamCodec (path: string) {
+ const { audioStream } = await audio.get(path)
+
+ if (!audioStream) return ''
+
+ const audioCodec = audioStream.codec_name
+ if (audioCodec === 'aac') return 'mp4a.40.2'
+
+ logger.warn('Cannot get audio codec of %s.', path, { audioStream })
+
+ return 'mp4a.40.2' // Fallback
+}