Add 30 fps limit in transcoding
[oweals/peertube.git] / server / helpers / ffmpeg-utils.ts
1 import * as ffmpeg from 'fluent-ffmpeg'
2 import { VideoResolution } from '../../shared/models/videos'
3 import { CONFIG, MAX_VIDEO_TRANSCODING_FPS } from '../initializers'
4
5 async function getVideoFileHeight (path: string) {
6   const videoStream = await getVideoFileStream(path)
7   return videoStream.height
8 }
9
10 async function getVideoFileFPS (path: string) {
11   const videoStream = await getVideoFileStream(path)
12
13   for (const key of [ 'r_frame_rate' , 'avg_frame_rate' ]) {
14     const valuesText: string = videoStream[key]
15     if (!valuesText) continue
16
17     const [ frames, seconds ] = valuesText.split('/')
18     if (!frames || !seconds) continue
19
20     const result = parseInt(frames, 10) / parseInt(seconds, 10)
21     if (result > 0) return result
22   }
23
24   return 0
25 }
26
27 function getDurationFromVideoFile (path: string) {
28   return new Promise<number>((res, rej) => {
29     ffmpeg.ffprobe(path, (err, metadata) => {
30       if (err) return rej(err)
31
32       return res(Math.floor(metadata.format.duration))
33     })
34   })
35 }
36
37 function generateImageFromVideoFile (fromPath: string, folder: string, imageName: string, size: string) {
38   const options = {
39     filename: imageName,
40     count: 1,
41     folder
42   }
43
44   if (size !== undefined) {
45     options['size'] = size
46   }
47
48   return new Promise<string>((res, rej) => {
49     ffmpeg(fromPath)
50       .on('error', rej)
51       .on('end', () => res(imageName))
52       .thumbnail(options)
53   })
54 }
55
56 type TranscodeOptions = {
57   inputPath: string
58   outputPath: string
59   resolution?: VideoResolution
60 }
61
62 function transcode (options: TranscodeOptions) {
63   return new Promise<void>(async (res, rej) => {
64     const fps = await getVideoFileFPS(options.inputPath)
65
66     let command = ffmpeg(options.inputPath)
67                     .output(options.outputPath)
68                     .videoCodec('libx264')
69                     .outputOption('-threads ' + CONFIG.TRANSCODING.THREADS)
70                     .outputOption('-movflags faststart')
71                     // .outputOption('-crf 18')
72
73     if (fps > MAX_VIDEO_TRANSCODING_FPS) command = command.withFPS(MAX_VIDEO_TRANSCODING_FPS)
74
75     if (options.resolution !== undefined) {
76       const size = `?x${options.resolution}` // '?x720' for example
77       command = command.size(size)
78     }
79
80     command.on('error', rej)
81            .on('end', res)
82            .run()
83   })
84 }
85
86 // ---------------------------------------------------------------------------
87
88 export {
89   getVideoFileHeight,
90   getDurationFromVideoFile,
91   generateImageFromVideoFile,
92   transcode,
93   getVideoFileFPS
94 }
95
96 // ---------------------------------------------------------------------------
97
98 function getVideoFileStream (path: string) {
99   return new Promise<any>((res, rej) => {
100     ffmpeg.ffprobe(path, (err, metadata) => {
101       if (err) return rej(err)
102
103       const videoStream = metadata.streams.find(s => s.codec_type === 'video')
104       if (!videoStream) throw new Error('Cannot find video stream of ' + path)
105
106       return res(videoStream)
107     })
108   })
109 }