127.0.0.1:8000 watch-together / master server / utils / ffmpeg.js
master

Tree @master (Download .tar.gz)

ffmpeg.js @masterraw · history · blame

const utils = require('.');
const log = require('./log').create_logger("ffmpeg")
const ffmpeg = require('fluent-ffmpeg');
// Optional path overrides in case ffmpeg is not part of the system path
process.env.FFMPEG_PATH && ffmpeg.setFfmpegPath(process.env.FFMPEG_PATH);
process.env.FFPROBE_PATH && ffmpeg.setFfprobePath(process.env.FFPROBE_PATH);
const FileType = require('file-type');
const path = require('path');
const ws_utils = require('../websocket/utils');
const broadcast = require('../websocket/broadcast');

const valid_video_formats = ['mp4', 'm4a', 'webm'];

module.exports = {
    /**
     * Converts video located at temp_file_path to a valid format if necessary.
     * Returns a Promise that resolves after conversion is complete.
     * 
     * Note: ffmpeg.format is a very long running process, so nothing with any sort
     * of timeout should depend on this promise (such as the response to a request)
     * @param {temp_file_path, name, video_id, user_id} data 
     */
    convert: function(data, options) {
        options = options || {};
        const video_dir = path.join(VIDEO_ROOT, data.video_id);
        return utils.fs.mkdir_if_not_exists(video_dir).then(function() {
            return FileType.fromFile(data.temp_file_path);
        }).then(function(file_info) {
            log.general.info("file_info:", file_info);
            const output_file_path = path.join(video_dir, 'video');

            if (valid_video_formats.includes(file_info.ext)) {
                log.v("We don't need to convert the file");
                return utils.fs.rename(data.temp_file_path, output_file_path);
            }

            log.general.info("convert", data);

            return new Promise(function(resolve, reject) {
                let percent_finished_x10 = 0;
                let conversion_format = valid_video_formats[0];
                ffmpeg(data.temp_file_path)
                    .format(conversion_format)
                    .on('end', resolve)
                    .on('error', reject)
                    .save(output_file_path)
                    .on('progress', function(progress) {
                        log.v(`Processing: ${data.name} - ${progress.percent}% done`);

                        // We update the user every tenth of a percent
                        let rounded_percent = Math.floor(progress.percent * 10);
                        if (rounded_percent > percent_finished_x10) {
                            percent_finished_x10 = rounded_percent;
                            broadcast({
                                command: "video-status",
                                data: {
                                    video_id: data.video_id,
                                    status: utils.status.video.converting,
                                    progress: percent_finished_x10 / 10
                                }
                            }, {
                                [data.user_id]: true
                            });

                            ws_utils.broadcast_storage();
                        }
                    });
            }).then(function() {
                if (options.delete_source) {
                    return utils.fs.unlink(data.temp_file_path);
                }
            });
        }).catch(function(error) {
            broadcast({
                command: "upload-error",
                data: {
                    video_id: data.video_id,
                    status: utils.status.video.converting,
                    message: "Error converting " + data.name
                }
            }, {
                [data.user_id]: true
            });

            return utils.reject(error);
        });
    }
};