UNPKG

uyem

Version:
459 lines 19.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const child_process_1 = require("child_process"); const date_fns_1 = require("date-fns"); const ffmpeg_static_1 = __importDefault(require("ffmpeg-static")); const isDev = process.env.FFMPEG_DEV === 'true'; if (isDev) { process.env.LOG_LEVEL = '1'; process.env.NODE_ENV = 'development'; } // eslint-disable-next-line import/first const lib_1 = require("./lib"); // eslint-disable-next-line import/first const constants_1 = require("./constants"); // eslint-disable-next-line import/first const interfaces_1 = require("../types/interfaces"); class FFmpeg { dirPath; time = 0; chunks; episodes = []; roomId; border = 5; videoWidth = 1280; videoHeight = 720; mapLength = 6; delimiter = '_'; forceOption = '-y'; inputOption = '-i'; filterComplexOption = '-filter_complex'; mapOption = '-map'; eol = ';'; backgroundInput = '0:v'; backgroundImagePath; // eslint-disable-next-line class-methods-use-this vstack = ({ inputs }) => `vstack=inputs=${inputs}`; // eslint-disable-next-line class-methods-use-this hstack = ({ inputs }) => `hstack=inputs=${inputs}`; // eslint-disable-next-line class-methods-use-this amerge = ({ count }) => `amerge=inputs=${count}`; overlay = 'overlay=(W-w)/2:(H-h)/2'; // eslint-disable-next-line class-methods-use-this pad = ({ x, y }) => `format=rgba,pad=width=iw+${x}:height=ih+${y}:x=iw/2:y=ih/2:color=#00000000`; // eslint-disable-next-line class-methods-use-this concat = ({ n, v, a }) => `concat=n=${n}:v=${v}:a=${a}`; // eslint-disable-next-line class-methods-use-this trim = ({ start, duration }) => `trim=start=${start}:duration=${duration},setpts=PTS-STARTPTS`; // eslint-disable-next-line class-methods-use-this atrim = ({ start, duration }) => `atrim=start=${start}:duration=${duration},asetpts=PTS-STARTPTS`; // eslint-disable-next-line class-methods-use-this scale = ({ w, h }) => `scale=w=${w}:h=${h}:force_original_aspect_ratio=decrease`; // eslint-disable-next-line class-methods-use-this color = ({ w, h }) => `color=c=black:s=${w}x${h}`; constructor({ dirPath, dir, roomId, backgroundImagePath, }) { this.dirPath = dirPath; this.backgroundImagePath = backgroundImagePath; this.roomId = roomId; this.chunks = (0, interfaces_1.createVideoChunks)({ dir, dirPath, indexShift: backgroundImagePath !== null }); } getFilterComplexArgument({ args, value, map, }) { return `${args}${value}${map}${this.eol}`; } async createVideo({ loading }) { const inputArgs = this.createInputArguments(); const filterComplexArgs = this.createFilterComplexArguments(); const args = inputArgs.concat(filterComplexArgs); const videosDirPath = this.getVideosDirPath(); const roomDir = (0, lib_1.getRoomDirPath)({ videosDirPath, roomId: this.roomId }); if (!fs_1.default.existsSync(roomDir)) { fs_1.default.mkdirSync(roomDir); } const name = this.getVideoName({ videosDirPath }); const src = path_1.default.resolve(roomDir, `./${name}`); args.push(src); // process.exit(0); const errorCode = await this.runFFmpegCommand(args, loading); return { errorCode, name, time: this.time, }; } getVideoName = ({ videosDirPath }) => `${this.dirPath .replace(videosDirPath, '') .replace(new RegExp(`^${this.roomId}${this.delimiter}`), '')}${interfaces_1.EXT_WEBM}`; getVideosDirPath = () => this.dirPath.replace(/[a-z0-9A-Z-_]+$/, ''); createInputArguments() { let args = [this.forceOption]; if (this.backgroundImagePath) { args = args.concat([this.inputOption, this.backgroundImagePath]); } this.chunks.forEach((item) => { args.push(this.inputOption); args.push(item.fullPath); }); return args; } // eslint-disable-next-line class-methods-use-this createMapArg(map) { return `[${map}]`; } getArg({ chunk, dest }) { const map = dest === 'a' ? chunk.mapA : chunk.map; return map !== '' ? this.createMapArg(map) : this.createMapArg(`${chunk.index}:${dest}`); } // eslint-disable-next-line class-methods-use-this joinFilterComplexArgs(args) { return `"${args.join('').replace(/;$/, '')}"`; } createFilterComplexArguments() { const args = []; const _episodes = (0, interfaces_1.createEpisodes)({ chunks: this.chunks }); let withAudio = false; this.episodes = _episodes.map((episode, index) => { console.log(episode); // eslint-disable-next-line @typescript-eslint/no-explicit-any const episodeCopy = { ...episode }; // Set start and duration let chunks = episode.chunks.map((chunk) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const chunkCopy = { ...chunk }; if (chunk.video) { chunkCopy.video = true; if (!episode.video) { episodeCopy.video = true; } } if (chunk.audio) { chunkCopy.audio = true; withAudio = true; if (!episode.audio) { episodeCopy.audio = true; } } if (chunk.start !== episode.start || chunk.end !== episode.end) { const start = index === 0 ? 0 : Math.abs(chunk.start - episode.start); const duration = episode.end - episode.start; if (chunk.video) { chunkCopy.map = (0, lib_1.createRandHash)(this.mapLength); args.push(this.getFilterComplexArgument({ args: this.createMapArg(`${chunk.index}:v`), value: this.trim({ start, duration }), map: this.createMapArg(chunkCopy.map), })); } if (chunk.audio) { chunkCopy.mapA = (0, lib_1.createRandHash)(this.mapLength); args.push(this.getFilterComplexArgument({ args: this.createMapArg(`${chunk.index}:a`), value: this.atrim({ start, duration }), map: this.createMapArg(chunkCopy.mapA), })); } } else { chunkCopy.map = ''; chunkCopy.mapA = ''; } return chunkCopy; }); // Set audio channels const mapA = (0, lib_1.createRandHash)(this.mapLength); let arg = ''; let audioCount = 0; chunks = chunks.map((chunk) => { const chunkCopy = { ...chunk }; if (chunk.audio) { audioCount++; arg += this.getArg({ chunk, dest: 'a' }); episodeCopy.mapA = mapA; return chunkCopy; } return chunk; }); if (audioCount !== 0) { args.push(this.getFilterComplexArgument({ args: arg, value: this.amerge({ count: audioCount }), map: this.createMapArg(mapA), })); } // Set video paddings const { videoCount } = (0, interfaces_1.getCountVideos)(episode.chunks); const { x, y, shiftX, shiftY } = (0, interfaces_1.getVideoShifts)({ videoCount, chunks, videoHeight: this.videoHeight, videoWidth: this.videoWidth, border: this.border, }); chunks = chunks.map((chunk) => { if (chunk.video) { const chunkCopy = { ...chunk }; // Scale if not included in size let map = (0, lib_1.createRandHash)(this.mapLength); if (shiftX > shiftY) { const newWidth = chunk.width - shiftX; args.push(this.getFilterComplexArgument({ args: this.getArg({ chunk: chunkCopy, dest: 'v' }), value: this.scale({ w: newWidth, h: -1 }), map: this.createMapArg(map), })); chunkCopy.map = map; } else if (shiftY) { const newHeight = chunk.height - shiftY; args.push(this.getFilterComplexArgument({ args: this.getArg({ chunk: chunkCopy, dest: 'v' }), value: this.scale({ w: -1, h: newHeight }), map: this.createMapArg(map), })); chunkCopy.map = map; } map = (0, lib_1.createRandHash)(this.mapLength); args.push(this.getFilterComplexArgument({ args: this.getArg({ chunk: chunkCopy, dest: 'v' }), value: this.pad({ x, y }), map: this.createMapArg(map), })); chunkCopy.map = map; return chunkCopy; } return chunk; }); // Set video stacks let map = (0, lib_1.createRandHash)(this.mapLength); if (videoCount === 2 || videoCount === 3) { arg = ''; chunks = chunks.map((chunk) => { if (chunk.video) { const chunkCopy = { ...chunk }; arg += this.getArg({ chunk, dest: 'v' }); chunkCopy.map = map; return chunkCopy; } return chunk; }); args.push(this.getFilterComplexArgument({ args: arg, value: this.hstack({ inputs: videoCount }), map: this.createMapArg(map), })); } else if (videoCount === 4) { arg = ''; let i = 0; chunks = chunks.map((chunk) => { if (chunk.video) { i++; if (i <= 2) { const chunkCopy = { ...chunk }; arg += this.getArg({ chunk, dest: 'v' }); chunkCopy.map = map; return chunkCopy; } } return chunk; }); args.push(this.getFilterComplexArgument({ args: arg, value: this.hstack({ inputs: 2 }), map: this.createMapArg(map), })); map = (0, lib_1.createRandHash)(this.mapLength); arg = ''; i = 0; chunks = chunks.map((chunk) => { if (chunk.video) { i++; if (i > 2) { const chunkCopy = { ...chunk }; arg += this.getArg({ chunk, dest: 'v' }); chunkCopy.map = map; return chunkCopy; } } return chunk; }); args.push(this.getFilterComplexArgument({ args: arg, value: this.hstack({ inputs: 2 }), map: this.createMapArg(map), })); map = (0, lib_1.createRandHash)(this.mapLength); episodeCopy.chunks = chunks; const uMaps = this.getUniqueMaps(episodeCopy); arg = ''; uMaps.forEach((uMap) => { arg += this.createMapArg(uMap); }); chunks = chunks.map((chunk) => { const chunkCopy = { ...chunk }; chunkCopy.map = map; return chunkCopy; }); args.push(this.getFilterComplexArgument({ args: arg, value: this.vstack({ inputs: 2 }), map: this.createMapArg(map), })); } episodeCopy.chunks = chunks; return episodeCopy; }); // Set overlay this.episodes = this.episodes.map((episode) => { const episdeCopy = { ...episode }; const uMaps = this.getUniqueMaps(episode); const map = (0, lib_1.createRandHash)(this.mapLength); const emptyMap = (0, lib_1.createRandHash)(this.mapLength); const isEmpty = uMaps.length === 1 && uMaps[0] === ''; if (isEmpty) { args.push(this.getFilterComplexArgument({ args: this.createMapArg(`${episode.chunks[0].index}:v`), value: this.scale({ w: constants_1.RECORD_WIDTH_DEFAULT, h: constants_1.RECORD_HEIGHT_DEFAULT }), map: this.createMapArg(emptyMap), })); } const trimMap = (0, lib_1.createRandHash)(this.mapLength); if (!this.backgroundImagePath) { const colorMap = (0, lib_1.createRandHash)(this.mapLength); args.push(this.getFilterComplexArgument({ args: '', value: this.color({ w: this.videoWidth, h: this.videoHeight }), map: this.createMapArg(colorMap), })); const duration = episode.end - episode.start; args.push(this.getFilterComplexArgument({ args: this.createMapArg(colorMap), value: this.trim({ start: episode.start, duration }), map: this.createMapArg(trimMap), })); } uMaps.forEach((uMap) => { args.push(this.getFilterComplexArgument({ args: `${this.createMapArg(this.backgroundImagePath ? this.backgroundInput : trimMap)}${this.createMapArg(isEmpty ? emptyMap : uMap)}`, value: this.overlay, map: this.createMapArg(map), })); }); episdeCopy.map = map; return episdeCopy; }); // Set concat const concatMap = (0, lib_1.createRandHash)(this.mapLength); const concatMapA = (0, lib_1.createRandHash)(this.mapLength); let arg = ''; this.episodes = this.episodes.map((episode) => { const episodeCopy = { ...episode }; arg += `${this.createMapArg(episode.map)}${episode.mapA ? this.createMapArg(episode.mapA) : ''}`; episodeCopy.map = concatMap; episodeCopy.mapA = concatMapA; return episodeCopy; }); args.push(this.getFilterComplexArgument({ args: arg, value: this.concat({ n: this.episodes.length, v: 1, a: withAudio ? 1 : 0, }), map: `${this.createMapArg(concatMap)}${withAudio ? this.createMapArg(concatMapA) : ''}`, })); const _args = [this.filterComplexOption, this.joinFilterComplexArgs(args)]; return _args.concat(this.getMap(withAudio)); } // eslint-disable-next-line class-methods-use-this getUniqueMaps(episode) { const uMaps = []; episode.chunks.forEach((_item) => { const { map } = _item; if (uMaps.indexOf(map) === -1) { uMaps.push(map); } }); return uMaps; } getMap(withAudio) { const maps = []; this.episodes.forEach((item) => { if (item.map) { const map = `"${this.createMapArg(item.map)}"`; if (maps.indexOf(map) === -1) { maps.push(this.mapOption); maps.push(map); } } if (item.mapA && withAudio) { const mapA = `"${this.createMapArg(item.mapA)}"`; if (maps.indexOf(mapA) === -1) { maps.push(this.mapOption); maps.push(mapA); } } }); return maps; } parseTime(data) { const time = data.match(/time=\d{2}:\d{2}:\d{2}/); let result = null; if (time) { const _time = time[0].replace('time=', ''); const t = _time.split(':'); const d = (0, date_fns_1.differenceInSeconds)(new Date(0, 0, 0, parseInt(t[0], 10), parseInt(t[1], 10), parseInt(t[2], 10)), new Date(0, 0, 0, 0, 0, 0)); const procents = Math.ceil(d / ((this.time - 1) / 100)); result = procents < 100 ? procents : 100; } return result; } async runFFmpegCommand(args, loading) { return new Promise((resolve) => { const command = `${ffmpeg_static_1.default} ${args.join(' ')}`; (0, lib_1.log)('info', 'Run command', command); const fC = (0, child_process_1.exec)(command, { env: process.env }, (error) => { if (error) { (0, lib_1.log)('error', 'FFmpeg command error', error); resolve(error.code || 0); } }); fC.stdout?.on('data', (d) => { (0, lib_1.log)('log', 'stdout', d); }); fC.stderr?.on('data', (d) => { (0, lib_1.log)('info', 'stderr', d); const time = this.parseTime(d); if (time) { loading(time); } }); fC.on('exit', (code) => { (0, lib_1.log)('info', 'FFmpeg command exit with code', code); resolve(code || 0); }); }); } } exports.default = FFmpeg; if (isDev) { const roomId = '1673340519949'; const dirPath = '/home/kol/Projects/werift-sfu-react/packages/server/rec/videos/1673340519949_1673950253377'; new FFmpeg({ dirPath, dir: fs_1.default.readdirSync(dirPath), roomId, backgroundImagePath: null, //backgroundImagePath: '/home/kol/Projects/werift-sfu-react/tmp/1png.png', }).createVideo({ loading: (time) => { // eslint-disable-next-line no-console console.log(time); }, }); } //# sourceMappingURL=ffmpeg.js.map